【Linux】socket网络编程之TCP编程

一、基础知识

socket网络编程主要有TCP编程和UCP编程两大类。
TCPUDP两种传输协议的不同所以分为两类不同代码的操作。
TCP:面向连接,可靠的,流式传输
UDP:无连接,不可靠,基于数据报的传输
需要导入的头文件:

# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<sys/types.h>//提供数据类型
# include<sys/socket.h>//提供socket编程的函数例如bind,listen等
# include<netinet/in.h>//提供sockaddr_in结构体
# include<arpa/inet.h>//inet_addr等函数

二、TCP编程的执行顺序

			服务端														客户端
			socket()													socket()
			bind()
			listen()													connect()
			accept()														
			recv()														send()
			send()														recv()		
			close()														close()

服务端
socket服务器端创建套接字
bind将套接字绑定到特定的地址。
listen创建监听队列,准备接受客户端连接请求。
accept阻塞并且等待客户端的连接请求,一旦有客户端接入,返回新的套接字与客户端进行连接。
recv从已连接的客户端接受数据
send向已经连接的客户端发送数据
close关闭连接
客户端
socket客户端创建套接字
connect连接服务器指定地址
send向已连接的服务端发送数据
recv从已连接的服务端获取数据
close关闭连接

三、三次握手,四次挥手

TCP协议中重要的三个标志报文
ACK 确认收到的数据或连接请求   -- 确认
SYN 建立连接请求				  -- 连接
FIN 终止连接请求               -- 结束

以上是三次握手四次挥手比较重要的三个报文。
三次握手
三次握手发生在客户端connect向服务端发送连接请求并等待回复阶段。

客户端向服务端发送SYN报文,表示希望建立连接。
服务端向客户端发送SYN+ACK报文,表示确认收到并且准备建立连接。
客户端向服务端发送ACK报文,表示确认连接。

以上就是三次握手过程,实现可靠的TCP连接。
四次挥手
四次挥手发生在服务端或者客户端一方想要关闭连接阶段。每执行一次close进行两次挥手
第一次close执行两次挥手:
客户端(服务端)执行close向服务端(客户端)发送FIN报文,表示希望关闭连接。
服务端(客户端)回复ACK报文,表示确认收到。
第二次close执行两次挥手:
服务端(客户端)执行close向客户端(服务端)发送FIN报文,表示希望关闭连接。
客户端(服务端)回复ACK报文,表示确认收到。

注意:
三次握手必须是客户端向服务端发送连接请求,四次挥手客户端还是服务端都可以先终止连接。
四次挥手可以是三次,这是特殊情况,在服务端和客户端同时执行close想要关闭连接就会只执行三次挥手。

四、粘包和TCP特性的可靠性

TCP协议是流式传输,所以会出现粘包现象。
粘包:一端多次send发送的数据被另一端一次recv接收

我们知道TCP的特性是:面向连接,可靠的,流式传输。
面向连接就是三次握手四次挥手,流式传输就是send recv
那么什么是TCP的可靠性呢?
应答确认,超时重传,滑动窗口,去重,乱序重排,流量控制

五、TIME_WAIT状态

TIME_WAIT状态一般是主动提出关闭的一方会出现的状态。一般持续两分钟,在这个状态下,这一端不能再被打开。
存在的原因:

  1. 可靠的终止TCP连接。
  2. 保证让迟来的报文有足够的时间被识别并被丢弃。

六、 分步骤代码实现

1. 服务端

1.1 创建套接字

1.创建套接字 socket(参数1,参数2,参数3)
	参数1:制定协议族:AF_INET:IPV4,AF_INET6:IPV6
	参数2:套接字类型:SOCK_STREAM:TCP,SOCK_DGRAM:UDP
	参数3:具体协议,通常写0
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
	exit(-1);//套接字创建失败
}

1.2 设置地址协议

2.设置地址协议
	1. 创建套接字结构体并且初始化清空0
	2. sin_family,制定协议族
	3. sin_port,使用htons函数将主机字节转化为网络字节
	4. sin_addr.s_addr,使用inet_addr("ip地址")将字符串IP地址转化为网络字节
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

1.3 bind 绑定套接字

3.bind绑定套接字  int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
	参数1:创建的套接字
	参数2:struct sockaddr*类型的第二步的结构体,大小
int res = bind(sockfd,(struct saddr*)&saddr,sizeof(saddr));

1.4 listen创建监听队列

4.listen创建监听队列 res = listen(参数1,参数2)  
	参数1:套接字
	参数2:监听队列大小
res = listen(sockfd,5);

1.5 accept接受客户端请求

5.accept接受客户端请求 int c = accept(sockfd, (struct sockaddr*)&caddr, &len)
	参数1:套接字
	参数2:struct sockaddr类型的客户端结构体
	参数3:客户端结构体大小,socklen_t len = sizeof(caddr);
struct saddr_in caddr;
socklen_t len = sizeof(caddr);
int c = accept(sockfd,(struct saddr*)&caddr,&len);

1.6 recv接收从客户端发送来的数据

recv从已经连接的套接字中接收数据 int n = recv(c, buff, 127, 0);
	参数1:accept中已经接收的套接字
	参数2:buff是一个数组,用于存储接收到的数据,使用前初始化并且分配足够的内存
	参数3:接收的最大字节数,需要根据buff设置的大小来具体设置
	参数4:标志位设置为0 表示默认接受行为
char buff[128] = {"0"};
recv(c,buff,127,0);

1.7 send向客户端发送数据

7.send向客户端发送数据send(c, "ok", 2, 0);
	参数1:accept函数获得的客户端套接字
	参数2:要发送的数据
	参数3:参数2的大小
	参数4:标志位0,表示默认发送行为
send(c,"ok",2,0);

1.8 关闭连接

close(c);
close(sockfd);

2. 客户端

2.1 创建套接字

创建套接字,同样是socket函数
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
	exit(1);
}

2.2 设置地址协议

struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htone(8000);
saddr.sin_addr.s_addr = inet_addr("172.0.0.1");

2.3 connect连接服务端

连接服务端 int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	参数1:套接字
	参数2:struct sockaddr类型的结构体指针和大小
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));

2.4 send向服务端发送数据

send发送数据send(sockfd,buff,strlen(buff),0)
	参数1:套接字
	参数2:要发送的数据存在buff中
	参数3:发送数据的大小
	参数4:标志位设置为0
char buff[128] = {""};
fgets(buff,128,stdin);
send(sockfd,buff,strlen(buff),0);

2.5 recv从服务端接收数据

recv接受服务端回复的数据recv(sockfd,buff,127,0)
	参数1:套接字
	参数2:存储接收到的数据
	参数3:接受的最大字节数
	参数4:标志位,设置为0表示默认接受
memset(buff,0,sizeof(buff));
recv(sockfd,buff,127,0);
printf("%s\n",buff);

2.6 close关闭

close(sockfd);

七、 总体服务端,客户端代码

服务端

# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<sys/socket.h>
# include<sys/types.h>
# include<arpa/inet.h>
# include<netinet/in.h>
int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1)
	{
		printf("套接字创建失败\n");
		exit(1);
	}
	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(8000);
	saddr.sin_addr.s_addr = inet_addr("127,0,0,1");

	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	if(res == -1)
	{	
		exit(1);
	}	
	res = listen(sockfd,5);
	struct sockaddr_in caddr;
	memset(&caddr,0,sizeof(caddr));
	socklen_t len = sizeof(caddr);
	int c = accept(sockfd,(struct saddr*)&caddr,&len));

	char buff[128] = {"0"};
	accept(c,buff,127,0);
	
	send(c,"ok",2,0);
	
	close(c);
	close(sockfd);
	
	return 0;
}

客户端

# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
# include<string.h>
# include<sys/types.h>
# include<sys/socket.h>
# include<arpa/inet.h>
# include<netinet/in.h>
int main()
{
	int sockfd = sock(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1)
	{
		exit(1);
	}
	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(8000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));

	char buff[128] = {"0"};
	fgets(buff,127,stdin);
	send(sockfd,buff,strlen(buff),0);

	memset(buff,0,sizeof(buff));
	recv(sockfd,buff,127,0);
	printf("%s\n",buff);
	
	close(sockfd);
	return 0;
}

八、TCP编程注意事项

牢记顺序,这样就能写出代码。
记住TCP协议的三大特性,面向连接,可靠的,流式传输。以及什么是面向连接,什么是可靠性,什么是流式传输。
三次握手,四次挥手。
粘包,以及如何解决粘包。
TIME_WAIT状态,以及存在的意义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LiuJWHHH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值