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;
}