共计 7331 个字符,预计需要花费 19 分钟才能阅读完成。
最近重温网络的时候,忽然发现,底层就那么些接口,java 必定也是封装了底层接口,看过我后面 Nio 相干的小伙伴必定晓得对这些类有点影响(Buffer,Channel,Selector,SelectionKey),可是跟底层对应不起来啊,这一篇就透过源码看一下,大略能帮忙你更好的理解这几个类,及底层的实现。并重个人兴趣向整顿,如有不适,欢送吐槽
Linux 网络编程
查阅材料的时候,发现 wiki 百科讲的曾经非常好了,我先贴下原文 Berkeley 套接字,相当完满的形容了 Socket 相干 Api 介绍及 demo 演示,因为,大学学的 c 语言都快还给老师了,写个 demo 未然不太事实,这里就臭不要脸的套用 wiki 百科的 demo,c 语言解说局部,如果有谬误欢送指出~~。
上面是精简版本的 linux 网络编程,具体版能够参考链接。
LinuxAPI
socket()
-
socket()
创立一个新的确定类型的套接字,返回套接字。- api:
int socket(int domain, int type, int protocol);
-
参数:
domain
: 为创立的套接字指定协定集 eg. IPV4type
: socket 类型 eg. 流,数据报文-
protocol
: 理论传输协定 eg. TCP,UDPbind()
- api:
-
bind()
为一个套接字调配地址。当应用socket()
创立套接字后,只赋予其所应用的协定,并未调配地址。在承受其它主机的连贯前,必须先调用 bind()为套接字调配一个地址。- api:
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
-
参数:
sockfd
: 套接字描述符,下面返回的套接字my_addr
: 指向 sockaddr 构造(用于示意所调配地址)的指针-
addrlen
: 用 socklen_t 字段指定了 sockaddr 构造的长度listen()
- api:
-
listen()
当 socket 和一个地址绑定之后,listen()
函数会开始监听可能的连贯申请。然而,这只能在有牢靠数据流保障的时候应用,例如:数据类型(SOCK_STREAM
,SOCK_SEQPACKET
)。- api:
int listen(int sockfd, int backlog);
-
参数:
sockfd
: 套接字描述符,下面返回的套接字backlog
: 实现三次握手、期待 accept 的全连贯的队列的最大长度下限。
- api:
accept()
-
accept()
当应用程序监听来自其余主机的面对数据流的连贯时,通过事件(比方 Unix select()零碎调用)告诉它。必须用accept()
函数初始化连贯。Accept() 为每个连贯创建新的套接字并从监听队列中移除这个连贯。它应用如下参数:- api:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
-
参数:
sockfd
: 监听的套接字描述符cliaddr
: 指向 sockaddr 构造体的指针,客户机地址信息。addrlen
: 指向socklen_t
的指针,确定客户机地址构造体的大小。
- api:
connect()
-
connect()
零碎调用为一个套接字设置连贯,参数有文件描述符和主机地址。链接到指定地址- api:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
-
参数:
sockfd
: 监听的套接字描述符serv_addr
: 指向 sockaddr 构造体的指针,服务器地址信息。addrlen
: 指向socklen_t
的指针,确定服务器地址构造体的大小。
- api:
select()
-
select()
: 在一段指定的工夫内,监听用户感兴趣的文件描述符上可读、可写和异样等事件- api:
int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
-
参数:
nfds
:没有用,仅仅为与伯克利 Socket 兼容而提供。readfds
:指定一个 Socket 数组,select 查看该数组中的所有 Socket。如果胜利返回,则 readfds 中寄存的是合乎‘可读性’条件的数组成员(如缓冲区中有可读的数据)。writefds
:指定一个 Socket 数组,select 查看该数组中的所有 Socket。如果胜利返回,则 writefds 中寄存的是合乎‘可写性’条件的数组成员(包含连贯胜利)。exceptfds
:指定一个 Socket 数组,select 查看该数组中的所有 Socket。如果胜利返回,则 cxceptfds 中寄存的是合乎‘有异样’条件的数组成员(包含连贯接失败)。timeout
:指定 select 执行的最长工夫,如果在 timeout 限定的工夫内,readfds、writefds、exceptfds 中指定的 Socket 没有一个符合要求,就返回 0。
- api:
poll()
-
poll()
用于查看套接字的状态。套接字能够被测试,看是否能够写入、读取或是有谬误。- api:
int poll(struct pollfd * fds , nfds_t nfds , int timeout);
-
参数:
fds
是 pollfd 构造体指针nfds
nfds 是描述符个数,构造体 pollfd 数组元素的个数timeout
: 参数设置为 - 1 时,示意永远阻塞期待。0 示意立刻返回,不阻塞。大于 0 时,示意期待指定数目的毫秒数。
- api:
fcntl()
-
fcntl()
关上文件描述符,具体操作由 cmd 决定- api:
int fcntl(int fd , int cmd , ... /* arg */);
-
参数
fd
:文件描述符cmd
:操作指令
- api:
epoll_create()
-
epoll_create()
在内核中创立epoll
实例并返回一个epoll
文件描述符。- api:
int epoll_create(int size);
-
参数:
size
: 而当初 size 曾经没有这种语义了,然而调用者调用时 size 仍然必须大于 0,以保障后向兼容性。
- api:
epoll_ctl()
-
epoll_ctl()
向 epfd 对应的内核epoll
实例增加、批改或删除对 fd 上事件 event 的监听。- api:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-
参数
epfd
epoll 构造体op
cud 对应的事件枚举fd
文件描述符events
程度触发 or 边缘触发
- api:
epoll_wait()
-
epoll_wait()
期待其治理的连贯上的 IO 事件- api:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-
参数
epfd
epoll 构造体events
epoll_event 构造体指针maxevents
最多返回多少事件timeout
当 timeout 为 0 时,epoll_wait 永远会立刻返回。而 timeout 为 -1 时,epoll_wait 会始终阻塞直到任一已注册的事件变为就绪。当 timeout 为一正整数时,epoll 会阻塞直到计时 timeout 毫秒完毕或已注册的事件变为就绪。
- api:
send()和 recv(), 或者 write()和 read(), 或者 recvfrom()和 sendto()等
- 用于往 / 从近程套接字发送和承受数据
相干 api 大抵如上,如有更精密的能够自行搜寻。
demo
BIO
/* Server code in C */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
struct sockaddr_in stSockAddr;
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(-1 == SocketFD)
{perror("can not create socket");
exit(EXIT_FAILURE);
}
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
stSockAddr.sin_addr.s_addr = INADDR_ANY;
if(-1 == bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
{perror("error bind failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
if(-1 == listen(SocketFD, 10))
{perror("error listen failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
for(;;)
{int ConnectFD = accept(SocketFD, NULL, NULL);
if(0 > ConnectFD)
{perror("error accept failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
/* perform read write operations ... */
shutdown(ConnectFD, SHUT_RDWR);
close(ConnectFD);
}
close(SocketFD);
return 0;
}
/* Client code in C */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
struct sockaddr_in stSockAddr;
int Res;
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == SocketFD)
{perror("cannot create socket");
exit(EXIT_FAILURE);
}
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
if (0 > Res)
{perror("error: first parameter is not a valid address family");
close(SocketFD);
exit(EXIT_FAILURE);
}
else if (0 == Res)
{perror("char string (second parameter does not contain valid ipaddress");
close(SocketFD);
exit(EXIT_FAILURE);
}
if (-1 == connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
{perror("connect failed");
close(SocketFD);
exit(EXIT_FAILURE);
}
/* perform read write operations ... */
shutdown(SocketFD, SHUT_RDWR);
close(SocketFD);
return 0;
}
NIO
#include<stdio.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<ctype.h>
#define MAXLEN 1024
#define SERV_PORT 8000
#define MAX_OPEN_FD 1024
int main(int argc,char *argv[])
{
int listenfd,connfd,efd,ret;
char buf[MAXLEN];
struct sockaddr_in cliaddr,servaddr;
socklen_t clilen = sizeof(cliaddr);
struct epoll_event tep,ep[MAX_OPEN_FD];
listenfd = socket(AF_INET,SOCK_STREAM,0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,20);
// 创立一个 epoll fd
efd = epoll_create(MAX_OPEN_FD);
tep.events = EPOLLIN;tep.data.fd = listenfd;
// 把监听 socket 先增加到 efd 中
ret = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
// 循环期待
for (;;)
{
// 返回已就绪的 epoll_event,- 1 示意阻塞, 没有就绪的 epoll_event, 将始终期待
size_t nready = epoll_wait(efd,ep,MAX_OPEN_FD,-1);
for (int i = 0; i < nready; ++i)
{
// 如果是新的连贯, 须要把新的 socket 增加到 efd 中
if (ep[i].data.fd == listenfd )
{connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
tep.events = EPOLLIN;
tep.data.fd = connfd;
ret = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
}
// 否则, 读取数据
else
{connfd = ep[i].data.fd;
int bytes = read(connfd,buf,MAXLEN);
// 客户端敞开连贯
if (bytes == 0){ret =epoll_ctl(efd,EPOLL_CTL_DEL,connfd,NULL);
close(connfd);
printf("client[%d] closed\n", i);
}
else
{for (int j = 0; j < bytes; ++j)
{buf[j] = toupper(buf[j]);
}
// 向客户端发送数据
write(connfd,buf,bytes);
}
}
}
}
return 0;
}
极度精简版本
nio 曾经很精简了就不多废话了
server
int main(void)
{
struct sockaddr_in stSockAddr;
// 创立套接字
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
// 构造体初始化
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
stSockAddr.sin_addr.s_addr = INADDR_ANY;
// 绑定地址
bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in));
// 监听申请
listen(SocketFD, 10);
// 承受申请
int ConnectFD = accept(SocketFD, NULL, NULL);
//do something
// 敞开
close(ConnectFD);
// 敞开
close(SocketFD);
return 0;
}
client
int main(void)
{
struct sockaddr_in stSockAddr;
int Res;
int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(1100);
//IP 地址转换
Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
// 连贯地址
connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in))
shutdown(SocketFD, SHUT_RDWR);
close(SocketFD);
return 0;
}
参考文章
Berkeley 套接字:https://zh.wikipedia.org/wiki…
epoll:https://zh.wikipedia.org/wiki…
linux 文档:https://man7.org/linux/man-pa…