linu环境下Socket通信流程
实际上是文件(内核的缓冲区)操作
服务端
socket tcp service
创建套接字
□ int lfd = socket
绑定本地IP和端口(本地ip和端口存储在sockaddr_in结构体中)
□ struct sockaddr_in serv;
□ serv.port = htons(port);
□ serv.IP= htonl(INADDR_ANY);(INADDR_ANY宏将适配当前IP)
//需要进行大端转小段的操作,即将主机字节区转网络字节区
□ bind(lfd, &serv, sizeof(serv));
监听
□ listen(lfd, 128);
128为同时能够监听到的最大连接个数
等待并接收连接请求
□ struct sockaddr_in client;
□ int len = sizeof(client);
□ int cfd = accept(lfd, &client, &len);
cfd - 用于通信的,接收和发送数据使用的文件描述符为cfd并不是lfd
通信
□ 接收数据: read/recv
□ 发送数据: write/send
关闭
□ close(lfd);
□ close(cfd);
客户端
创建套接字
int fd = socket
连接服务器
struct sockaddr_in server;
server.port
server.ip=(int) (需要将点分十进制转换成int类型)
server.family
connect(fd,&server,sizeof(server));
通信
□ 接收数据: read/recv
□ 发送数据: write/send
关闭
□ close(lfd);
□ close(cfd);
例子:
在本机上实现SERVER和CLIENT通信,从C发送字符串给S,S收到后将其转换成大写并回复,C将
接收到的字符输出
server服务端源码
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char* argv[])
{
// 创建用于监听的套节字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket error");
exit(1);
}
// 绑定
struct sockaddr_in serv_addr;
// init
memset(&serv_addr, 0, sizeof(serv_addr));
// bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 地址族协议 ipv4
serv_addr.sin_port = htons(9999); // 本地端口, 需要转换为大端
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 0 是用本机的任意IP
int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
// 设置监听
ret = listen(lfd, 64);
if(ret == -1)
{
perror("listen error");
exit(1);
}
// 等待并接受连接请求
struct sockaddr_in cline_addr;
socklen_t clien_len = sizeof(cline_addr);
int cfd = accept(lfd, (struct sockaddr*)&cline_addr, &clien_len);
if(cfd == -1)
{
perror("accept error");
exit(1);
}
char ipbuf[64];
// int -> char*
printf("cliient ip: %s, port: %d\n",
inet_ntop(AF_INET, &cline_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
ntohs(cline_addr.sin_port));
// 通信
while(1)
{
// 先接收数据
char buf[1024] = {0};
int len = read(cfd, buf, sizeof(buf));
if(len == -1)
{
perror("read error");
break;
}
else if(len > 0)
{
// 顺利读出了数据
printf("read buf = %s\n", buf);
// 小写 -》 大写
for(int i=0; i
client客户端源码
#include
#include
#include
#include
#include
#include
#include
#include
// tcp client
int main(int argc, const char* argv[])
{
// 创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("socket error");
exit(1);
}
// 连接服务器
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret == -1)
{
perror("connect error");
exit(1);
}
// 通信
while(1)
{
// 写数据
// 接收键盘输入
char buf[512];
fgets(buf, sizeof(buf), stdin);
// 发送给服务器
write(fd, buf, strlen(buf)+1);
// 接收服务器端的数据
int len = read(fd, buf, sizeof(buf));
printf("read buf = %s, len = %d\n", buf, len);
}
return 0;
}
标志位介绍
SYN: 请求建立连接
ACK: 应答
FIN: 断开连接
连接需要三次握手:在实际的socket编程中,这三次握手会自动完成,并不需要我们进行实际操作
第一次握手
客户端
携带标志位: SYN
可以携带数据()
随机产生32为序号
服务器
检测SYN值是否为1
服务器:
第一次握手是由客户端发起的连接请求,在第一次发送的数据包中在标志位中将含有SYN信号,请求与服务器建立连接,并含有随机产生的32位序号,可以携带数据,在服务器接收到该数据包后,将对该数据包进行校验,首先校验SYN位是否为1,是则第一次握手成功
第二次握手
服务器
ACK 标志位 + 确认序号
客户端随机序号+1
发起一个连接请求
SYN+32随机序号
客户端
检测标志位: 1
校验: 确认序号是否正确
在第一次握手成功后,服务器将回复一个数据包,在该数据包中的标志位为ACK,应答客户端的第一个数据包接收成功,紧接着是一个32位的确认信号,该信号是在第一次接收的数据包中的32位随机序号加1进位,除了确认序号ACK外,服务器会在该数据包中产生一个SYN信号以及32位的随机序号,可以携带数据,请求与客户端建立连接,在客户端接收到该数据包后,依次检测ACK信号,确认信号,SYN信号是否正确,如果无误,则第二次握手成功
第三次握手
在前两次握手成功的基础上,客户端继续向服务器发送数据包,包含应答信号ACK和上一次服务器产生的随机序号进1位确认序号,服务器检测ACK是否为1,校验确认序号是否正确,如果无误,则客户端和服务器端正式建立连接
客户端:
应答信号ACK
32位确认序号
服务器
检测ACK是否为1
校验确认序号是否正确
四次挥手
挥手过程即为客户端和服务端断开连接的请求过程,哪一端主动断开连接都可以,需要一个标志位FIN,和对方最后发送ACK相应信号时携带的确认序号。
挥手过程:以客户端断开请求为例,
第一次挥手:
客户端:
发送断开连接的请求,在该数据包中含有:
FIN+序号(对方最后回复ACK时携带的确认序号)
ACK+序号
服务端:
检验SYN值是否为1,
ACK的作用:告诉对方之前发送的数据接收了多少
第二次挥手:
在接收到第一次挥手发送的包后,服务器回复一个确认的数据包,包含ACK+确认序号(FIN对应的序号+1)。
服务器:
给client确认数据包
ACK+确认编号
FIN对应的序号+1+携带数据大小
客户端:
检测ACK值和确认序号
第三次挥手
第三次挥手是由对方发起的,在前两次挥手成功后,对方也将发送一个断开连接请求的数据包,包含FIN+序号,ACK+序号,收到该数据包后自身将进行检验
服务器:
发送断开连接的请求
FIN+序号
ACK+序号
客户端
数据检测
第四次挥手
过程与第二次大致一致
在程序代码中,客户端和服务端都调用了colse()方法后,四次挥手的过程就算是成功了。
文章主要摘录自传智Linux网络编程的讲义,并记录下自己学习过程中的一些理解