0voice-2.1.1-网络io
bind 和 listen
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET; //`AF_INET` 代表 `IPv4` , `AF_INET6` 代表 `IPv6`。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定网卡地址,0.0.0.0。
servaddr.sin_port = htons(2000); //0 - 1023端口是系统默认的,从 1024 开始使用。
if (-1 == bind(sockfd , (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf("bind failed: %s\n",strerror(errno));
}
listen(sockfd , 10); //设置待处理连接队列的长度 (backlog = 10)
printf("listen finshed\n");
- 总的来说就是,监听本地网络所有可用网络接口上的
2000
端口 。 INADDR_ANY
是通配地址0.0.0.0
, 代表监听所有地址,当然这里的参数也可以填写本地的其他IP
地址 。- 注意不能反复绑定同一个端口 。
sockfd
代表的是服务器的公共入口,它在等待任何潜在的客户端。它不属于任何一个特定的“用户”或“会话”,其职责职责是产生那些最终会与userinfo
建立映射关系的fd
(即clientfd
)。后文也会说明一个sockfd
能对应多个clientfd
。- 监测端口的命令
netstat -anop | grep 2000
。
accept 和 recv
- 版本 \(1\)
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
printf("accept\n");
int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
printf("accept finished\n");
char buffer[1024] = {0};
int count = recv(clientfd , buffer , 1024 , 0);
printf("RECV %s\n", buffer);
count = send(clientfd , buffer , count , 0);
printf("SEND: %d\n", count);
- 注意
accept
和accept finished
是不同的,accept()
是会进行阻塞的。 accept
产生clientfd
。- 这个版本的缺陷就是只能建立一次
accept
,如果有第二个客户端请求服务,则无法accept finished
。
- 版本 \(2\)
while (1) {
printf("accept\n");
int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
printf("accept finished\n");
char buffer[1024] = {0};
int count = recv(clientfd , buffer , 1024 , 0);
printf("RECV %s\n", buffer);
count = send(clientfd , buffer , count , 0);
printf("SEND: %d\n", count);
}
- 缺陷:单线程的模式,当有 \(1\) , \(2\) , \(3\)客户顺序请求
accept
的时候,只有当 \(1\) 客户 \(recv\) 完毕的时候,\(2\) , \(3\)客户才能accept finished
, 在之前所有的accept
和recv
请求只能被阻塞。
- 版本 \(3\)
void *client_thread(void *arg) {
int clientfd = *(int *)arg; // 从传入的参数中获取 clientfd
while (1) { //防止只能接受一条
char buffer[1024] = {0};
int count = recv(clientfd , buffer , 1024 , 0);
printf("RECV %s\n", buffer);
if (count == 0) { //discount
printf("client disconnect: %d\n",clientfd);
close(clientfd);
break;
}
count = send(clientfd , buffer , count , 0);
printf("SEND: %d\n", count);
}
}
while (1) {
printf("accept\n");
int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
printf("accept finished\n");
pthread_t thid;
// 创建一个新的线程
// 参数1: 存储新线程的ID
// 参数2: 线程属性 (NULL表示使用默认属性)
// 参数3: 新线程将执行的函数 (这里是 client_thread)
// 参数4: 传递给 client_thread 函数的参数 (这里是新接受的 clientfd 的地址)
pthread_create(&thid , NULL , client_thread , &clientfd);
}
- 为每一个
clientfd
创造一个新的线程,一个线程服务于一个客户端。
补充
-
当
recv
回馈是0
的时候,代表断开连接。 -
\(fd\) 的值有依次增加
3,4,5,6
( 无论是sockfd
还是clientfd
)0,1,2
在哪里? 系统已经封装好了ls /dev/stdin -l
,ls /dev/stdout -l
和ls /dev/stderr -l
ulimit -a
: 看每一个进程io
的数量,进程的fd
是有限的。close(fd)
回收和分配的机制,有回收的时间,分配会分配最小的那个未被使用的fd
select
-
之前的模式:\(1\) 请求 , \(1\) 线程
- 好处:代码逻辑简单
- 缺点:不利于并发,1 \(k\) 并发量左右
-
select
提供文件集合
fd_set
,集合的大小