1. TCP的连接与断开

1.1 创建连接过程(三次握手)

* 客户端向服务器发送连接请求SYN
* 服务器接收到连接请求SYN后, 向客户端发送收到指令ACK和连接请求SYN
* 客户端收到服务器发送的ACK和SYN后向服务器发送收到指令ACK

1.2 断开连接过程(四次挥手)

* 客户端向服务器发送断开请求FIN
* 服务器接收到客户端发送的断开请求FIN后向客户端发送收到指令ACK
* 服务器检查是否还有没有收发完的数据, 如果数据已经收发完毕, 服务器向客户端发送断开请求FIN
* 客户端接收到服务器发来的断开请求后, 检查是否还有没有接收完的数据,如果没有就向服务器发送收到指令ACK

 2. TCP与UDP的区别

* TCP有连接, UDP没有连接
* TCP是数据流, UDP是数据报文
* TCP收发数据相对慢, UDP收发数据相对快(局域网内传输数据用UDP相对较好,它可以极大限度地利用带宽)
* TCP安全,稳定,可靠;UDP不安全,不稳定,不可靠(安全: 数据相对不容易被窃取    稳定: 几乎没有传输速率的变化   可靠: 一定能收到数据)
* TCP有序(先发送的数据先到, 后发送的数据后到), 数据有边界;UDP无序(可能后发送的数据会先到),数据无边界
3. IO多路复用之select

3.1 select函数
//select函数原型 //监视放在里面的描述符号,有反应返回1, 没有反应返回-1 int select(int nfds,
//描述符号数量,最大描述符号数加一 fd_set *readfds, //描述符号集合(读取) fd_set *writefds, //描述符号集合(写入)
fd_set *exceptfds, //描述符号集合(异常) struct timeval *timeout); //延时 void FD_CLR(int
fd,fd_set *set); //将fd从set中删除 int FD_ISSET(int fd,fd_set *set);
//判断fd是否在set中(是返回非0,否返回0) void FD_SET(int fd,fd_set *set); //将fd添加到set中 void
FD_ZERO(fd_set *set); //将set置为0(清空)
3.2 select函数实现监视标准输入 0  
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include
<sys/select.h> #include <fcntl.h> int main(){ fd_set fds; //描述符号集合
FD_ZERO(&fds); //置零 FD_SET(0,&fds); //将标准输入设备 0 添加到描述符号集合 int r; char
buff[1024] = {0}; while(1){ //使用一次阻塞替代多次阻塞 r = select(1,&fds,NULL,NULL,NULL);
if(r > 0){ printf("%d有动静!\n",r); scanf("%s",buff); printf("接收到了:%s\n",buff); }
} return 0; }
 

3.3 select函数实现服务器连接多个客户端

 服务器(server)端
//服务器端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include
<string.h> #include <signal.h> #include <sys/select.h> #include <fcntl.h>
//最多允许的客户端数量 #define NUM 100 int serverSocket,clientSocket[NUM]; int currentNum
= 0; //当前客户端数量 void hand(int val){ //7. 关闭连接 for(int i = 0;i < NUM; i++){ if(-1
!= clientSocket[i]) close(clientSocket[i]); } close(serverSocket); printf("bye
bye!\n"); exit(0); } int main(int argc,char* argv[]){ if(argc != 3)
printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一:
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 serverSocket = socket(AF_INET,SOCK_STREAM,0);
if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1);
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in sAddr = { 0 };
sAddr.sin_family = AF_INET; //协议类型 和socket函数第一个参数一致 sAddr.sin_addr.s_addr =
inet_addr(argv[1]); //将字符串转整数 sAddr.sin_port = htons(atoi(argv[2]));
//将字符串转整数,再将小端转换成大端 //3. 绑定服务器协议地址簇 int r = bind(serverSocket,(struct
sockaddr*)&sAddr,sizeof sAddr); if(-1 == r)
printf("绑定失败:%m\n"),close(serverSocket),exit(-2); printf("绑定成功!\n"); //4. 监听 r
= listen(serverSocket,10); //数量 if(-1 == r)
printf("监听失败:%m\n"),close(serverSocket),exit(-3); printf("监听成功!\n");
//初始化客户端描述符号数组 for (int i = 0; i < NUM; ++i){ clientSocket[i] = -1; } //开始监视
//不仅需要监视serverSocket还要监视每一个返回回来的clientSocket fd_set fds; int maxFd; //最大描述符号
struct sockaddr_in cAddr = {0}; int len = sizeof(cAddr); int cfd; char
buff[1024] = {0}; maxFd = 0; maxFd = ((maxFd > serverSocket) ? maxFd :
serverSocket); while(1){ FD_ZERO(&fds); //清空 FD_SET(serverSocket,&fds);
//将服务器socketFd放到监视集合之中 //将客户端socketFd放到监视集合之中 for (int i = 0; i < NUM; ++i){
if(-1 != clientSocket[i]){ FD_SET(clientSocket[i],&fds); } } //开始监视 r =
select(maxFd+1,&fds,NULL,NULL,NULL); if(-1 == r)
printf("服务器崩溃:%m\n"),close(serverSocket),exit(-1); else if(0 == r){
printf("服务器处于等待状态!\n"); continue; }else{ //检查是不是serverSocket的动静
if(FD_ISSET(serverSocket,&fds)){ cfd = accept(serverSocket,NULL,NULL); if(-1 ==
cfd){ printf("客户端连接失败!\n"); }else{ printf("有客户端连接上服务器了:%d\n",cfd); //保存客户端描述符号
for (int i = 0; i < NUM; ++i){ if(-1 == clientSocket[i]){ clientSocket[i] =
cfd; maxFd = ((maxFd > cfd) ? maxFd : cfd); break; } } } } } //检查客户端是否有动静 for
(int i = 0; i < NUM; ++i){ if(-1 != clientSocket[i] &&
FD_ISSET(clientSocket[i],&fds)){ r = recv(clientSocket[i],buff,1023,0); if(r >
0){ buff[r] = 0; printf("%d >> %s\n",clientSocket[i], buff); }else{
printf("客户端: %d 已经断开连接了\n",clientSocket[i]); clientSocket[i] = -1; } } } }
return 0; }
 客户(Client)端
//客户端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include
<string.h> #include <signal.h> int clientSocket; void hand(int val){ //5. 关闭连接
close(clientSocket); printf("bye bye!\n"); exit(0); } int main(int argc,char*
argv[]){ if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一:
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 clientSocket = socket(AF_INET,SOCK_STREAM,0);
if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1);
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in cAddr = { 0 };
cAddr.sin_family = AF_INET; cAddr.sin_addr.s_addr = inet_addr(argv[1]);
//将字符串转整数 cAddr.sin_port = htons(atoi(argv[2])); //将字符串转整数,再将小端转换成大端 //3.连接服务器
int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr); if(-1 ==
r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2); printf("连接服务器成功!\n");
//4. 通信 char buff[256] = {0}; while(1){ printf("你想要发送:"); scanf("%s",buff);
send(clientSocket,buff,strlen(buff),0); } return 0; }
 

3.4 select函数实现简单聊天室 

 服务器(Server)端
//服务器端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include
<string.h> #include <signal.h> #include <sys/select.h> #include <fcntl.h>
//最多允许的客户端数量 #define NUM 100 int serverSocket,clientSocket[NUM]; int currentNum
= 0; //当前客户端数量 void hand(int val){ //7. 关闭连接 for(int i = 0;i < NUM; i++){ if(-1
!= clientSocket[i]) close(clientSocket[i]); } close(serverSocket); printf("bye
bye!\n"); exit(0); } int main(int argc,char* argv[]){ if(argc != 3)
printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一:
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 serverSocket = socket(AF_INET,SOCK_STREAM,0);
if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1);
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in sAddr = { 0 };
sAddr.sin_family = AF_INET; //协议类型 和socket函数第一个参数一致 sAddr.sin_addr.s_addr =
inet_addr(argv[1]); //将字符串转整数 sAddr.sin_port = htons(atoi(argv[2]));
//将字符串转整数,再将小端转换成大端 //3. 绑定服务器协议地址簇 int r = bind(serverSocket,(struct
sockaddr*)&sAddr,sizeof sAddr); if(-1 == r)
printf("绑定失败:%m\n"),close(serverSocket),exit(-2); printf("绑定成功!\n"); //4. 监听 r
= listen(serverSocket,10); //数量 if(-1 == r)
printf("监听失败:%m\n"),close(serverSocket),exit(-3); printf("监听成功!\n");
//初始化客户端描述符号数组 for (int i = 0; i < NUM; ++i){ clientSocket[i] = -1; } //开始监视
//不仅需要监视serverSocket还要监视每一个返回回来的clientSocket fd_set fds; int maxFd; //最大描述符号
struct sockaddr_in cAddr = {0}; int len = sizeof(cAddr); int cfd; char
buff[1024] = {0}; maxFd = 0; maxFd = ((maxFd > serverSocket) ? maxFd :
serverSocket); while(1){ FD_ZERO(&fds); //清空监视集合 FD_SET(serverSocket,&fds);
//将服务器socketFd放到监视集合之中 //将客户端socketFd放到监视集合之中 for (int i = 0; i < NUM; ++i){
if(-1 != clientSocket[i]){ FD_SET(clientSocket[i],&fds); } } //开始监视 r =
select(maxFd+1,&fds,NULL,NULL,NULL); if(-1 == r)
printf("服务器崩溃:%m\n"),close(serverSocket),exit(-1); else if(0 == r){
printf("服务器处于等待状态!\n"); continue; }else{ //检查是不是serverSocket的动静
if(FD_ISSET(serverSocket,&fds)){ cfd = accept(serverSocket,NULL,NULL); if(-1 ==
cfd){ printf("客户端连接失败!\n"); }else{ printf("有客户端连接上服务器了:%d\n",cfd); //保存客户端描述符号
for (int i = 0; i < NUM; ++i){ if(-1 == clientSocket[i]){ clientSocket[i] =
cfd; maxFd = ((maxFd > cfd) ? maxFd : cfd); break; } } } } } //检查客户端是否有动静 for
(int i = 0; i < NUM; ++i){ if(-1 != clientSocket[i] &&
FD_ISSET(clientSocket[i],&fds)){ r = recv(clientSocket[i],buff,1023,0); if(r >
0){ buff[r] = 0; printf("%d >> %s\n",clientSocket[i], buff);
//服务器将数据转发给每一个在线的客户端(除了发消息给服务器的客户端) char tBuff[2048];
sprintf(tBuff,"来自%d客户端发给服务器的消息:%s",clientSocket[i],buff); for(int j = 0; j <
NUM; j++){ if(-1 != clientSocket[j] && clientSocket[i] != clientSocket[j]){
send(clientSocket[j],tBuff,strlen(tBuff),0); } } }else{ printf("客户端: %d
已经断开连接了\n",clientSocket[i]); clientSocket[i] = -1; } } } } return 0; }
 客户(Client)端

 
//客户端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include
<sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include
<string.h> #include <signal.h> int clientSocket; void hand(int val){ //5. 关闭连接
close(clientSocket); printf("bye bye!\n"); exit(0); } int main(int argc,char*
argv[]){ if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0); printf("ip: %s
port:%d\n",argv[1],atoi(argv[2])); signal(SIGINT,hand); //1. 创建socket 参数一:
协议类型(版本) 参数二: 通信媒介 参数三: 保护方式 clientSocket = socket(AF_INET,SOCK_STREAM,0);
if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1);
printf("创建socket成功!\n"); //2. 创建服务器协议地址簇 struct sockaddr_in cAddr = { 0 };
cAddr.sin_family = AF_INET; cAddr.sin_addr.s_addr = inet_addr(argv[1]);
//将字符串转整数 cAddr.sin_port = htons(atoi(argv[2])); //将字符串转整数,再将小端转换成大端 //3.连接服务器
int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr); if(-1 ==
r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2); printf("连接服务器成功!\n");
//开始监视 //不仅要监视标准输入设备, 还要监视clientSocket服务器是否发送数据 fd_set fds; int maxFd =
clientSocket > 0 ? clientSocket : 0; char buff[2048] = {0}; while(1){ //清空集合
FD_ZERO(&fds); //将标准输入输出放入到集合中 FD_SET(0,&fds); //将clientSocket放入到监视集合中
FD_SET(clientSocket,&fds); //开始监视 r = select(maxFd + 1, &fds, NULL,NULL,NULL);
if(-1 == r) printf("客户端崩溃:%m\n"),close(clientSocket),exit(-1); else if(0 == r){
printf("客户端处于等待状态!\n"); continue; }else{ memset(buff,0,2048); //如果 0
有动静就向服务器发消息 if(FD_ISSET(0,&fds)){ scanf("%s",buff);
send(clientSocket,buff,strlen(buff),0); continue; } //如果
clientSocket有动静就接收服务器发来的消息 if(FD_ISSET(clientSocket,&fds) && -1 !=
clientSocket){ memset(buff,0,2048); printf("服务器发来了客户端的消息!\n"); r =
recv(clientSocket,buff,2047,0); if(r > 0){ buff[r] = 0; printf("服务器发来消息 >>
%s\n",buff); } } } } return 0; }

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信