socketFd:是一个描述符号,表示进程中的网络设备
分两种:
1.socket函数创建,创建的socketFd接口对应服务器端的网络设备,用来接受客户端连接
2.accept函数返回值,创建的socketFd接口对应客户端(一个socket对应一个客户端)
对socketFd进行读,从客户端接收数据
写,发送数据到客户端
tcp客户端则是只有socket函数创建的socketFd,对应服务器(注意与上面两种进行区分)
对socketFd进行读,从服务器接收数据
写,发送数据到服务器
协议地址簇:用于区分主机、进程
三个成员:
family 通信协议
addr ip地址 区分主机
端口号 区分进程
绑定:把 socketfd 和 地址协议簇 绑到一起
监听:绑定后准备好accept
accept:形成稳定的数据传输通道
阻塞式等待和有名管道类似
tcp:有连接
建立连接:三次握手
1.客户端向服务器发送连接请求(报文SYN)
2.服务器收到SYN之后向客户端发送收到指令ACK和连接请求SYN
3.客户端向服务器发送收到指令ACK
断开连接:四次挥手
1.客户端向服务器发送断开请求 FIN
2.服务器收到客户端发来的FIN后回复收到指令ACK
3.服务器检查是否存在没有收发完的数据
如果有,先收发完,然后再向客户端发送断开指令/请求 FIN
4.客户端收到服务器发来的FIN后,检查是否存在没有收发完的数据
如果有,先收发完,然后回复ACK
服务器主动断开只需要第3、4步
如果因为网络问题双方被动断开,会延迟等待一段时间(期间保持连接时候的状态),之后若再没回复连接就主动断开
tcp的11种状态:
CLOSED 初始状态 服务器准备建立连接前和断开连接后
LISTEN 监听状态 服务器绑定监听后的状态
SYN_RCVD 服务器接收SYN之后,发送ACK之前
SYN_SENT 客户端发送SYN后,接收SYN和ACK之前
ESTABLISHED 连接状态 服务器和客户端都有的一个状态,建立稳定的数据传输通道时的状态
FIN_WAIT1 服务器收到FIN之后,发送ACK之前
FIN_WAIT2 服务器发送FIN之后,接受ACK之前
CLOSE_WAIT 客户端接收FIN之后,发送ACK之前
LAST_ACK 客户端接收ACK之后
CLOSEING 双方同时发送FIN,等待接收ACK
TIME_WAIT 等待延迟
tcp编程模型
服务器 客户端
1.创建socket 1.创建socket(socket)
2.确定服务器协议地址簇 2.获取服务器地址簇(struct sockaddr)
3.绑定(bind)
4.监听(listen)
5.接受客户端连接(accept) 3.连接服务器(connect)
6.通信 4.通信
==收(recv)发(send)都可以==
7.断开连接 5.断开连接(close)
UDP:无连接
编程模型:
server client
1.创建socket 1.创建socket
socket 第二个参数变为SOCK_DGRAM
2.协议地址簇 2.协议地址簇
3.绑定
4.通信 3.通信
通常使用recvfrom、sendto
recvfrom == recv + accep 除了接收数据外,还可获取对方协议地址簇
sendto == send + connect 除了发送数据外,还可以连接到对方(精准发送)
5.close 4.close
tcp聊天室:
1.服务器要能接受多个客户端连接
如果按照之前的tcp测试代码,可以发现s可以连接c1,当c2打开时提示连接服务器成功,但是s收不到c2发来的信息
原因:server只accept了一次,所以只有c1对应的fd被保存了下来
如果先循环多次接收多个客户端连接
那么就会卡在连接状态,到不了通信循环
解决思路:
1)父子进程
行不通,因为子进程只是拷贝父进程代码和进程上下文,父子进程socket函数的返回值是不同的两个
2)多进程
弊端:多进程开销相对很大
3) 多线程
可行,但有弊端:第四步搞不定,且做不到高并发
2.服务器能收到客户端发送的消息
因为scanf会阻塞,所以用select实现同时接受连接和通信
3.服务器能转发给所有客户端新消息(业务)
与2同理
4.服务器能知道客户端断开了连接
tcp相关代码
//server.c
#include <stdio.h>
#include <unistd.h> //linux操作系统标准头文件
#include <string.h> //memcpy
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h> //umask
#include <time.h>
#include <sys/time.h>
#include <dirent.h> //遍历目录
#include <fcntl.h> //文件映射虚拟内存
#include <sys/mman.h>
#include <wait.h>
#include <signal.h> //SIGCHLD
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int clientFd;
int serverFd;
void hand(int s)
{
if (2 == s)
{
// 7.断开连接
close(clientFd);
close(serverFd);
printf("断开连接\n");
exit(1);
}
}
int main()
{
signal(2, hand);
// 1.创建socket
serverFd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == serverFd)
{
printf("创建socket失败%m\n");
return -1;
}
printf("创建socket成功\n");
// 2.确定服务器协议地址簇
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // ip地址 注意字符串转网络字节序
addr.sin_port = htons(9999); // 端口号 用10000左右的 注意大小端转换
// 3.绑定(bind)
int r = bind(serverFd, (struct sockaddr *)&addr, sizeof addr);
if (-1 == r)
{
printf("绑定失败%m\n");
close(serverFd);
return -1;
}
printf("绑定成功\n");
// 4.监听(listen)
r = listen(serverFd, 100);
if (-1 == r)
{
printf("监听失败%m\n");
close(serverFd);
return -1;
}
printf("监听成功\n");
// 5.接受客户端连接(accept)
struct sockaddr_in cAddr={0};//用来接收客户端的协议地址簇
int len = sizeof cAddr;
clientFd = accept(serverFd, (struct sockaddr*)&cAddr, &len);
if (-1 == clientFd)
{
printf("接受客户端连接失败%m\n");
close(serverFd);
return -1;
}
printf("接受客户端连接成功 %d %s %u\n",clientFd,inet_ntoa(cAddr.sin_addr),cAddr.sin_port);
// 6.通信
char buff[1024];
int n=0;
char temp[1024];
while (1)
{
r = recv(clientFd, buff, 1023, 0);
if (r > 0)
{
buff[r] = 0; // 添加字符串结束符号
printf("接受到%d字节消息:%s\n", r, buff);
sprintf(temp,"%d-%s",n++,buff);
send(clientFd,temp,strlen(temp),0);
}
}
return 0;
}
//client.c
int clientFd;
void hand(int s)
{
if (2 == s)
{
// 5.断开连接
close(clientFd);
printf("断开连接\n");
exit(1);
}
}
int main()
{
signal(2, hand);
// 1.创建socket
clientFd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == clientFd)
{
printf("创建socket失败%m\n");
return -1;
}
printf("创建socket成功\n");
// 2.确定服务器协议地址簇
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // ip地址 注意字符串转网络字节序
addr.sin_port = htons(9999); // 端口号 用10000左右的 注意大小端转换
// 3.连接服务器
int r = connect(clientFd, (struct sockaddr *)&addr, sizeof addr);
if (-1 == r)
{
printf("连接服务器失败%m\n");
close(clientFd);
return -1;
}
printf("连接服务器成功\n");
// 4.通信
char buff[1024];
char temp[1024];
while (1)
{
printf("请输入要发送的内容:");
scanf("%s", buff);
r = send(clientFd, buff, strlen(buff), 0);
printf("发送%d字节数据到服务器\n",r);
r=recv(clientFd,temp,1023,0);
if (r > 0)
{
printf("服务器回复%d字节消息:%s\n", r, temp);
}
}
return 0;
}
udp相关代码
//server.c
int serverFd;
void hand(int s)
{
if (2 == s)
{
// 7.断开连接
close(serverFd);
printf("断开连接\n");
exit(1);
}
}
int main()
{
signal(2, hand);
// 1.创建socket
serverFd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == serverFd)
{
printf("创建socket失败%m\n");
return -1;
}
printf("创建socket成功\n");
// 2.确定服务器协议地址簇
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("111.67.205.228");
addr.sin_port = htons(9999);
// 3.绑定(bind)
int r = bind(serverFd, (struct sockaddr *)&addr, sizeof addr);
if (-1 == r)
{
printf("绑定失败%m\n");
close(serverFd);
return -1;
}
printf("绑定成功\n");
// 4.通信
char buff[1024];
int n = 0;
char temp[1024];
struct sockaddr_in cAddr = {0};
int len = sizeof cAddr;
while (1)
{
// r = recv(serverFd, buff, 1023, 0);
r = recvfrom(serverFd, buff, 1023, 0, (struct sockaddr *)&cAddr, &len);
if (r > 0)
{
buff[r] = 0; // 添加字符串结束符号
printf("接受到%d字节消息:%s\n", r, buff);
memset(temp,0,1024);
sprintf(temp, "%d-%s", n++, buff);
// printf("%s\n",temp);
sendto(serverFd, temp, strlen(temp), 0, (struct sockaddr *)&cAddr, len);
}
}
return 0;
}
//client.c
int clientFd;
void hand(int s)
{
if (2 == s)
{
// 5.断开连接
close(clientFd);
printf("断开连接\n");
exit(1);
}
}
int main()
{
signal(2, hand);
// 1.创建socket
clientFd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == clientFd)
{
printf("创建socket失败%m\n");
return -1;
}
printf("创建socket成功\n");
// 2.确定服务器协议地址簇
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("111.67.205.228");
addr.sin_port = htons(9999);
// 4.通信
char buff[1024];
char temp[1024];
while (1)
{
printf("请输入要发送的内容:");
scanf("%s", buff);
int r = sendto(clientFd, buff, strlen(buff), 0,(struct sockaddr*)&addr,sizeof addr);
printf("发送%d字节数据到服务器\n", r);
memset(temp,0,1024);
r = recv(clientFd, temp, 1023, 0);
if (r > 0)
{
printf("服务器回复%d字节消息:%s\n", r, temp);
}
}
return 0;
}