2、SocketImpl源码分析
从上一章的讲解可以看出ServerSocket就是针对SocketImpl的一个外观类,所以这章重点讲述java对SocketImpl的实现,会涉及C、C++代码所以需要阅读者有相应的基础,当然笔者也会使用最简单的语言进行描述,C大佬可跳过描述。
abstract class SocketImpl implements SocketOptions
SocketImpl实现了SocketOptions接口,而从名字就可以看出这个接口就是对Socket进行配置的,下方就是配置项也是在接口中声明的final属性。
选项 | 类型 | 意义 |
---|---|---|
SO_BROADCAST | BOOL | 允许套接口传送广播信息。 |
SO_DEBUG | BOOL | 记录调试信息。 |
SO_DONTLINER | BOOL | 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。 |
SO_DONTROUTE | BOOL | 禁止选径;直接传送。 |
SO_KEEPALⅣE | BOOL | 发送“保持活动”包。 |
SO_LINGER | struct linger FAR* | 如关闭时有未发送数据,则逗留。 |
SO_OOBINLINE | BOOL | 在常规数据流中接收带外数据。 |
SO_RCVBUF | int | 为接收确定缓冲区大小。 |
SO_REUSEADDR | BOOL | 允许套接口和一个已在使用中的地址捆绑(参见bind())。 |
SO_SNDBUF | int | 指定发送缓冲区大小。 |
TCP_NODELAY | BOOL | 禁止发送合并的Nagle算法。 |
SO_TIMEOUT | int | 服务器连接超时时间。 |
SocketOptions接口除了声明了一些常量以外还有两个方法getOption、setOption,两个方法都是获取当前socket的配置值得包括上一章介绍的获取和设置缓冲区大小也是设置SocketOptions里的SO_RCVBUF属性,毕竟这里仅仅是一些声明并没有具体可说唯一值得需要关注的就是上方的那张表格。
上方代码可以看出SocketImpl是一个抽象类,里面基本是属性的声明和抽象方法的声明具体请看下表。
属性名 | 类型 | 简单描述 |
---|---|---|
socket | Socket | 这个属性在ServerSocket中并没有使用,暂且忽略 |
serverSocket | ServerSocket | 在ServerSocket的讲解中就可以看出传入是当前对象this |
fd | FileDescriptor | Socket的文件描述符,下方重点介绍 |
address | InetAddress | 启动服务时监听的端口地址信息 |
localport | int | 监听的端口 |
port | int | 连接的端口号,如果是客户端则是客户端端口,如果是服务accept获取的socket则是远程请求连接的端口 |
fd属性:
这个属性比较重要,在windows api中的socket函数的返回值就是他官方描述:socket returns a descriptor referencing the new socket。
他将返回一个嵌套字描述符。而在linux下的socket方法也是返回的描述符。下面将详细对比两者描述符从而更清楚的理解他。
在讲解之前需要先学习FileDescriptor这个类。下面将是他属性列表。
属性名 | 类型 | 简单描述 |
---|---|---|
fd | int | 描述符 |
看到上方列表读者可能会疑惑为什么我看到的是四个属性而我这里只列举了一个,emmm...那是因为在Socket源码解读篇并没有使用到,相信看完本讲读后读者会自己看懂那些个属性和方法的用处,这里暂不多说。
fd属性:
可以在表中看出他是int类型的,而在SocketImpl中的fd属性的时候有说winapi和linux的区别下面将列举出两者源码(这里只讲windows、linux下的场景对比以后也会如此)。
windows
在windows中socket方法返回的是SOCKET 一个宏定义,那么他是什么?
typedef u_int SOCKET;
可以从上方代码看出他是u_int类型的别名(ps:u_int 则是unsigned int 代表无符号int,有符号无符号如果读者不清楚需要自己去补充这方面知识,因为就光这个就能再写一篇文章),int?一个文件描述符为什么是int呢?因为在windows编程下主要使用的是句柄而句柄就是一串int数字这个数字标识这一个内存中的类(这里可以理解在系统中是键值对而这个int则是key,而他获取的对象则就是我们使用accept或bind所需要的),暂时可以这么理解因为即使是内存也是键值对的map这里暂时不解释指针因为那玩意一说有可能是一章,所以各位读者暂时将这个int当做一个key值而调用其他socket方法都是需要这个key值的没有这个key是操作不了的。
linux
在linux中socket方法比windows要直接,他直接返回的是int类型
int __sys_socket(int family, int type, int protocol)
可能你会觉得为什么在linux中我调用的试socket怎么我给的是__sys_socket是不是我出错了,并不是的笔者这里暂时挖个坑后续再说socket调用的时候自会填坑。
从上面windows和linux代码可以看出两者都是int那么久简单了在java中自然也就是int,可能有人会疑问为啥在windows是无符号在linux是有符号的,其实在linux下也是无符号的只是并没有进行标注,由下方linux源代码可看出。
static int sock_map_fd(struct socket *sock, int flags)
{
struct file *newfile;
//从此处获取未使用的fd最终调用的是__alloc_fd方法
int fd = get_unused_fd_flags(flags);
//如果返回值为false 则释放当前socket说明分配失败
if (unlikely(fd < 0)) {
sock_release(sock);
return fd;
}
//创建新的fd,在linux下任何文件都可以当做file所以也包括socket所以这里叫做file也不为过。
newfile = sock_alloc_file(sock, flags, NULL);
if (likely(!IS_ERR(newfile))) {
//如果创建成功则插入到当前进程的属性中并且计数加一
// 在linux下所有模块都是动态加载的当计数到0的时候则会卸载模块后续或详细讲解
fd_install(fd, newfile);
return fd;
}
put_unused_fd(fd);
return PTR_ERR(newfile);
}
int __alloc_fd(struct files_struct *files,
unsigned start, unsigned end, unsigned flags)
{
unsigned int fd;
}
上面都是针对linux和windows的讲解那么在java中是怎么描述的呢?
jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
int fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
上面的代码是从SocketImpl中的bind方法的jni实现中的,可以看出他先获取了FileDescriptor对象也就是SocketImpl的fd,然后再根据FileDescriptor获取到他的fd我们最终使用的文件描述符,之前大篇幅说了fd是int的所以这里即int。 由于内容过多所以本章到此结束,下一章将会讲解SocketImpl的实现,因为一开始据说明了当前类是抽象类他自然存有实现。
网友评论