目录
TCP协议
TCP通讯时序
三次握手
主动发起连接请求端,发送SYN标志位,请求建立连接。 携带序号、数据字节数(0)、滑动窗口大小。
被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。
主动发起连接请求端,发送ACK标志位,应答服务器连接请求。携带确认序号。
巧记:
女:我喜欢你。
男:好,我知道了,我也喜欢你。
女:好,我也知道了,那我们在一起吧。
四次挥手
主动关闭连接请求端, 发送FIN标志位。
被动关闭连接请求端, 应答ACK标志位。(半关闭完成)
被动关闭连接请求端, 发送FIN标志位。
主动关闭连接请求端, 应答ACK标志位。(连接全部关闭)
巧记:
女:我不喜欢你了,我们分手吧,还有什么想说的赶紧说吧。
男:好,我知道了,我也不喜欢你了。
男:我没什么想说的了,我们分手吧。
女:好,我也知道了,我们以后不要再联系了。
滑动窗口
mss:一条数据的最大数据量。
win:滑动窗口
1:第1次握手。
2:第2次握手,应答,滑动窗口的空间为6k。
3:第3次握手。
4~9:连续发送6次数据,每次发送1k的数据。
10:应答接收到6k的数据。win 2048:处理完2k的数据,空出2k的数据空间。
11:应答接收到6k的数据。win 4096:已经处理了4k的数据,空出了4k的空间。
12:发送1k的数据。
13:发送数据完成,第1次挥手。
14~16:处理接收到的数据,第2次挥手。
17:第3次挥手。
18:第4次挥手。
测试代码1
多进程服务器接收多个客户端数据,并返回。
/*
多进程并发服务器
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#define DuanKouHao 8080
void ChuLi_HanShu() //处理函数
{
while ((waitpid(0, NULL, WNOHANG)) > 0) //非阻塞回收
;
return;
}
int main(int argc, char *argv[])
{
int fd_FWQ; //服务器文件描述符
int fd_KHD; //客户端文件描述符
int flag;
struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
socklen_t KHD_DaXiao; //客户端大小
pid_t JinCheng_ID; //进程ID
int ZiJie_DaXiao; //字节大小
char data[1024];
char Show_Data[1024]; //显示的数据
clock_t start, stop; //clock_t为clock()函数返回的变量类型
double duration;
bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);
DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);
flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
if (flag == -1)
{
perror("绑定服务器地址结构错误");
exit(1);
}
flag = listen(fd_FWQ, 6); //设置连接上限
if (flag == -1)
{
perror("设置连接上限错误");
exit(1);
}
KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);
while (1)
{
fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接
if (fd_KHD == -1)
{
perror("建立客户端连接错误");
exit(1);
}
JinCheng_ID = fork(); //创建子进程
if (JinCheng_ID < 0)
{
perror("创建子进程错误");
exit(1);
}
else if (JinCheng_ID == 0) //子进程
{
close(fd_FWQ);
break;
}
else if (JinCheng_ID > 0) //父进程,注册信号捕捉函数
{
struct sigaction ChuLi_FangShi;
ChuLi_FangShi.sa_handler = ChuLi_HanShu; //捕捉信号后的处理函数
sigemptyset(&ChuLi_FangShi.sa_mask); //清空信号集
ChuLi_FangShi.sa_flags = 0; //默认处理方式
flag = sigaction(SIGINT, &ChuLi_FangShi, NULL);
if (flag == -1)
{
perror("注册处理函数错误");
exit(1);
}
close(fd_KHD);
}
}
if (JinCheng_ID == 0) //子进程
{
while (1)
{
ZiJie_DaXiao = read(fd_KHD, &data, sizeof(data)); //读取客户端数据
if (ZiJie_DaXiao == -1)
{
if (errno == EINTR)
{
continue;
}
else
{
perror("读取数据错误");
exit(1);
}
}
sprintf(Show_Data, "这是%d号服务器子进程,接收到的数据是%s\n", getpid(), data);
write(fd_KHD, Show_Data, strlen(Show_Data)); //发回给客户端
write(STDOUT_FILENO, Show_Data, strlen(Show_Data)); //显示到终端
}
}
return 0;
}
测试结果
Address already in use解决方法
netstat -apn | grep 端口号
批量杀进程
ps aux | grep 运行的文件 | awk '{print $2}' | xargs kill -9
测试代码2
单进程服务器与千级数量级的客户端建立连接。
/*
CeShi2_FWQ.c
单进程服务器与千级数量级的客户端建立连接
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>
#define DuanKouHao 8080
void ChuLi_HanShu() //处理函数
{
while ((waitpid(0, NULL, WNOHANG)) > 0) //非阻塞回收
;
return;
}
int main(int argc, char *argv[])
{
int fd_FWQ; //服务器文件描述符
int fd_KHD; //客户端文件描述符
int flag;
struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
socklen_t KHD_DaXiao; //客户端大小
pid_t JinCheng_ID; //进程ID
int ZiJie_DaXiao; //字节大小
char data[1024];
char Show_Data[1024]; //显示的数据
clock_t start, stop; //clock_t为clock()函数返回的变量类型
double duration;
bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);
DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);
flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
if (flag == -1)
{
perror("绑定服务器地址结构错误");
exit(1);
}
//flag = listen(fd_FWQ, 10); //设置连接上限
flag = listen(fd_FWQ, 100); //设置连接上限
if (flag == -1)
{
perror("设置连接上限错误");
exit(1);
}
KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);
flag = 0;
start = clock();
while (1)
{
fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接
if (fd_KHD == -1)
{
perror("建立客户端连接错误");
exit(1);
}
flag++;
printf("第%d次连接,客户端IP地址是:%s\n", flag,
inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, data, sizeof(DiZhi_JieGou_KHD))); //打印客户端信息
close(fd_KHD);
if (flag >= 2000)
{
break;
}
}
stop = clock();
duration = (double)(stop - start) / 1000.0; //CLK_TCK为clock()函数的时间单位,即时钟打点
printf("时间:%f\n", duration);
return 0;
}
/*
CeShi2_KHD.c
多进程客户端测试服务器连接代码
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#define FWQ_IP "10.3.22.7" //服务器IP
int main(int argc, char *argv[])
{
int fd_FWQ; //服务器
int flag;
struct sockaddr_in FWQ_DiZhi; //服务器地址
int ZiFuShu; //字符数
char data[1024]; //数据
pid_t JinCheng_ID; //进程ID
FWQ_DiZhi.sin_family = AF_INET; //IPv4
FWQ_DiZhi.sin_port = htons(8080); //端口号8080
flag = inet_pton(AF_INET, FWQ_IP, &FWQ_DiZhi.sin_addr); //十进制IP转换网络IP
if (flag == -1)
{
perror("十进制IP转换网络IP错误");
exit(1);
}
flag = 0;
while (1)
{
JinCheng_ID = fork(); //创建子进程
if (JinCheng_ID < 0)
{
perror("创建子进程错误");
exit(1);
}
else if (JinCheng_ID == 0) //子进程
{
break;
}
flag++;
if (flag >= 2000)
{
break;
}
}
if (JinCheng_ID == 0) //子进程
{
printf("这是%d子进程\n", getpid());
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
sleep(5);
flag = connect(fd_FWQ, (struct sockaddr *)&FWQ_DiZhi, sizeof(FWQ_DiZhi));
if (flag == -1)
{
perror("连接服务器错误");
exit(1);
}
close(fd_FWQ);
}
while(1);
return 0;
}
测试结果
同时连接服务器的客户端上限为10时。
同时连接服务器的客户端上限为100时。
测试代码4
多线程服务器与千级数量级的客户端建立连接。
/*
CeShi4_FWQ.c
多线程并发服务器处理客户端数据,并发回去
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>
#define DuanKouHao 8080 //端口号
#define XianCheng_XinXi_ShuLiang 2000 //线程信息数量
#define data_DaXiao 1024 //数据缓冲区大小
struct XianCheng_XinXi //线程信息
{
struct sockaddr_in KHD_XinXi;
int fd_KHD;
};
void *ChuLi_HanShu(void *arg) //处理函数
{
int leng;
char data[data_DaXiao];
struct XianCheng_XinXi *xian_cheng_xin_xi = (struct XianCheng_XinXi *)arg;
while (1)
{
leng = read(xian_cheng_xin_xi->fd_KHD, data, sizeof(data));
if (leng == -1)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
continue;
}
else
{
break;
}
}
}
close(xian_cheng_xin_xi->fd_KHD);
return (void *)0;
}
int main(int argc, char *argv[])
{
int fd_FWQ; //服务器文件描述符
int fd_KHD; //客户端文件描述符
int flag;
int flag1;
struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
socklen_t KHD_DaXiao; //客户端大小
pthread_t XianCheng_ID; //线程ID
int ZiJie_DaXiao; //字节大小
char data[1024];
clock_t start, stop; //clock_t为clock()函数返回的变量类型
double duration;
struct XianCheng_XinXi xian_cheng_xin_xi[XianCheng_XinXi_ShuLiang];
bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);
DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);
flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
if (flag == -1)
{
perror("绑定服务器地址结构错误");
exit(1);
}
flag = listen(fd_FWQ, 128); //设置连接上限
if (flag == -1)
{
perror("设置连接上限错误");
exit(1);
}
KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);
flag1 = 0;
start = clock();
while (1)
{
fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接
if (fd_KHD == -1)
{
perror("建立客户端连接错误");
exit(1);
}
flag1++;
printf("第%d次连接,客户端IP地址是:%s\n", flag1,
inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, data, sizeof(DiZhi_JieGou_KHD))); //打印客户端信息
xian_cheng_xin_xi[fd_KHD].KHD_XinXi = DiZhi_JieGou_KHD;
xian_cheng_xin_xi[fd_KHD].fd_KHD = fd_KHD;
pthread_create(&XianCheng_ID, NULL, ChuLi_HanShu, (void *)&xian_cheng_xin_xi[fd_KHD]);
pthread_detach(XianCheng_ID);
close(fd_KHD);
if (flag1 >= 2000)
{
break;
}
}
stop = clock();
duration = (double)(stop - start) / 1000.0; //CLK_TCK为clock()函数的时间单位,即时钟打点
printf("时间:%f\n", duration);
return 0;
}
/*
CeShi4_KHD.c
多线程并发服务器处理客户端数据,并发回去
*/
/*
测试1,多进程客户端测试服务器连接代码
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#define FWQ_IP "10.3.22.7" //服务器IP
int main(int argc, char *argv[])
{
int fd_FWQ; //服务器
int flag;
struct sockaddr_in FWQ_DiZhi; //服务器地址
int ZiFuShu; //字符数
char data[1024]; //数据
pid_t JinCheng_ID; //进程ID
FWQ_DiZhi.sin_family = AF_INET; //IPv4
FWQ_DiZhi.sin_port = htons(8080); //端口号8080
flag = inet_pton(AF_INET, FWQ_IP, &FWQ_DiZhi.sin_addr); //十进制IP转换网络IP
if (flag == -1)
{
perror("十进制IP转换网络IP错误");
exit(1);
}
flag = 0;
while (1)
{
JinCheng_ID = fork(); //创建子进程
if (JinCheng_ID < 0)
{
perror("创建子进程错误");
exit(1);
}
else if (JinCheng_ID == 0) //子进程
{
break;
}
flag++;
if (flag >= 2000)
{
break;
}
}
if (JinCheng_ID == 0) //子进程
{
printf("这是%d子进程\n", getpid());
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
sleep(10);
flag = connect(fd_FWQ, (struct sockaddr *)&FWQ_DiZhi, sizeof(FWQ_DiZhi));
if (flag == -1)
{
perror("连接服务器错误");
exit(1);
}
//close(fd_FWQ);
}
//while (1);
sleep(180);
close(fd_FWQ);
if(JinCheng_ID>0){
sleep(30);
}
return 0;
}
测试结果
TCP状态转换
CLOSED:初始状态。
LISTEN:服务器端的某个SOCKET处于监听状态,可以接受连接。
SYN_SENT:客户端已发送SYN报文。
SYN_RCVD:接收到SYN报文,服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。
ESTABLISHED:连接已经建立。
FIN_WAIT_1:等待对方的FIN报文,当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。
FIN_WAIT_2:等待对方的FIN报文,socket只能接收数据,不能发。主动关闭链接的一方,发出FIN收到ACK以后进入该状态,称之为半连接或半关闭状态。
TIME_WAIT:收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING:双方都正在关闭SOCKET连接。
CLOSE_WAIT:在等待关闭。
LAST_ACK:被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。
主动发起连接请求端
CLOSE
发送SYN
进入SYN_SENT状态
接收ACK、SYN
发送ACK
进入ESTABLISHED状态(数据通信态)
主动关闭连接请求端
ESTABLISHED状态(数据通信态)
发送FIN
进入FIN_WAIT_1状态
接收ACK
进入FIN_WAIT_2状态(半关闭)
接收对端发送FIN
回发ACK
进入TIME_WAIT状态(只有主动关闭连接方,会经历该状态)
等2MSL时长
CLOSE
被动接收连接请求端
CLOSE
进入LISTEN状态
接收SYN
发送ACK、SYN
进入SYN_RCVD状态
接收ACK
进入ESTABLISHED状态(数据通信态)
被动关闭连接请求端
ESTABLISHED状态(数据通信态)
接收FIN
发送ACK
进入CLOSE_WAIT状态(说明对端【主动关闭连接端】处于半关闭状态)
发送FIN
进入LAST_ACK状态
接收ACK
CLOSE
2MSL时长
一般为40s,保证最后一个 ACK 能成功被对端接收。一定出现在主动关闭连接请求端。
端口复用
测试代码5
使用端口复用,解除服务器主动断开连接后的2MSL等待时长。
/*服务器端代码*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
int main(int argc, char *argv[])
{
int flag;
int fd_FWQ; //服务器文件描述符
int fd_KFD; //客户端文件描述符
char data[1024]; //读取的数据
int ZiJieShu; //字节数
struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
socklen_t KeHuDuan_DaXiao; //客户端大小
char KHD_IP[1024]; //客户端IP
char FWQ_IP[1024]; //服务器IP
int opt = 1;
bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
setsockopt(fd_FWQ, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用
DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
DiZhi_JieGou_FWQ.sin_port = htons(8080); //端口号8080
DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY); //获取系统中任意有效的IP地址
flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器的地址结构
if (flag == -1)
{
perror("绑定服务器地址结构错误");
exit(1);
}
printf("服务器IP:%s,端口号:%d\n",
inet_ntop(AF_INET, &DiZhi_JieGou_FWQ.sin_addr.s_addr, FWQ_IP, sizeof(FWQ_IP)),
ntohs(DiZhi_JieGou_FWQ.sin_port));
flag = listen(fd_FWQ, 128); //设置连接服务器上限数
if (flag == -1)
{
perror("设置连接上限数错误");
exit(1);
}
KeHuDuan_DaXiao = sizeof(DiZhi_JieGou_KHD);
fd_KFD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KeHuDuan_DaXiao); //阻塞监听客户端连接
if (fd_KFD == -1)
{
perror("阻塞监听客户端连接错误");
exit(1);
}
printf("客户端IP:%s,端口号:%d\n",
inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, KHD_IP, sizeof(KHD_IP)), //网络转换成十进制本地IP
ntohs(DiZhi_JieGou_KHD.sin_port)); //网络转换成本地端口
while (1)
{
ZiJieShu = read(fd_KFD, data, sizeof(data));
write(STDOUT_FILENO, data, ZiJieShu); //终端显示
write(fd_KFD, data, ZiJieShu);
}
close(fd_KFD);
close(fd_FWQ);
return 0;
}
测试结果
半关闭
通信双方中,只有一端关闭通信。在关闭多个文件描述符应用的文件时,采用全关闭方法。
man 2 shutdown
参数sockfd
文件描述符。
参数how
SHUT_RD:关闭读端
SHUT_WR:关闭写端
SHUT_RDWR:关闭读写端
select多路IO转换
借助内核, select 来监听, 客户端连接、数据通信事件。
FD_ZERO
清空一个文件描述符集合。
man FD_ZERO
参数set
文件描述符集合。
FD_SET
将待监听的文件描述符,添加到监听集合中。
man FD_SET
参数fd
文件描述符。
参数set
文件描述符集合。
用法:
fd_set rset;
FD_SET(3, &rset);
FD_SET(5, &rset);
FD_SET(6, &rset);
FD_CLR
将一个文件描述符从监听集合中移除。
man FD_CLR
参数fd
文件描述符。
参数set
文件描述符集合。
FD_ISSET
判断一个文件描述符是否在监听集合中。
man FD_ISSET
参数fd
文件描述符。
参数set
文件描述符集合。
返回值
1:在
0:不在
用法:
fd_set rset;
int flag;
flag=FD_ISSET(4,&rset);
select
man select
参数nfds
监听的所有文件描述符中,最大文件描述符+1。
参数readfds
读,文件描述符监听集合。传入、传出参数。
参数writefds
写,文件描述符监听集合。传入、传出参数。
参数exceptfds
异常,文件描述符监听集合。传入、传出参数。
参数timeout
大于0:设置监听超时时长。
NULL:阻塞监听。
0:非阻塞监听,轮询。
返回值
大于0:所有监听集合(3个)中, 满足对应事件的总数。
0:没有满足监听条件的文件描述符。
-1:errno
优缺点
缺点:监听上限受文件描述符限制,最大1024。检测满足条件的fd,自己添加业务逻辑,提高了编码难度。
优点:跨平台。win、linux、macOS、Unix、类Unix、mips。
测试代码6
服务器利用select进行监听客户端。
/*服务器端代码*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int fd_FWQ; //服务器文件描述符
int fd_KHD; //客户端文件描述符
int fd_Max; //最大的文件描述符
struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
int opt = 1;
int flag, i, leng;
fd_set fd_all_JIHe; //所有的文件描述符集合
fd_set fd_Du_JiHe; //读的文件描述符集合
socklen_t JieGouTi_ChangDu; //结构体长度
char data[1024];
bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零
fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
if (fd_FWQ == -1)
{
perror("创建服务器套接字错误");
exit(1);
}
setsockopt(fd_FWQ, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用
DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
DiZhi_JieGou_FWQ.sin_port = htons(8080); //端口号
DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY); //获取可用IP地址
flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
if (flag == -1)
{
perror("绑定服务器地址结构错误");
exit(1);
}
flag = listen(fd_FWQ, 128); //设置连接服务器上限数
if (flag == -1)
{
perror("设置连接服务器上限数错误");
exit(1);
}
FD_ZERO(&fd_all_JIHe); //清空文件描述符集合
FD_SET(fd_FWQ, &fd_all_JIHe); //添加服务器文件描述符,监听服务器
fd_Max = fd_FWQ;
while (1)
{
fd_Du_JiHe = fd_all_JIHe;
flag = select(fd_Max + 1, &fd_Du_JiHe, NULL, NULL, NULL); //监听
if (flag == -1)
{
perror("监听错误");
exit(1);
}
if (FD_ISSET(fd_FWQ, &fd_Du_JiHe))
{
JieGouTi_ChangDu = sizeof(DiZhi_JieGou_KHD);
fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &JieGouTi_ChangDu); //与客户端连接,不会阻塞
if (fd_KHD == -1)
{
perror("客户端建立错误");
exit(1);
}
FD_SET(fd_KHD, &fd_all_JIHe); //添加连接好客户端的文件描述符,监听读事件
if (fd_Max < fd_KHD) //最大的文件描述符小于连接上服务器的文件描述符
{
fd_Max = fd_KHD;
}
if (flag == 1) //说明只有一个文件描述符,是服务器的文件描述符
{
continue;
}
}
for (i = fd_FWQ + 1; i <= fd_Max; i++)
{
if (FD_ISSET(i, &fd_Du_JiHe))
{
leng = read(i, &data, sizeof(data));
if (leng == -1)
{
if (errno == EINTR)
{
continue;
}
else
{
perror("读取文件错误");
exit(1);
}
}
else if (leng == 0) //客户端关闭
{
close(i);
FD_CLR(i, &fd_all_JIHe); //移除客户端文件描述符
}
else //接收到数据
{
write(i, "你好客户端,我是服务器,接收到的数据是:", sizeof("你好客户端,我是服务器,接收到的数据是:"));
write(i, data, leng);
write(STDOUT_FILENO, "接收到的数据是:", sizeof("接收到的数据是:"));
write(STDOUT_FILENO, data, leng);
}
}
}
}
close(fd_FWQ);
return 0;
}