网络通信_TCP客户端与服务器

本文详细介绍了TCP协议的基本概念,包括TCP头部结构、端口号、序号、窗口大小等关键字段的作用及意义。并通过示例代码展示了如何使用Winsock库在Windows环境下实现TCP客户端和服务端的具体编程过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TCP协议介绍

TCP协议头介绍:

(1)端口号[16bit]

我们知道,网络实现的是不同主机的进程间通信。在一个操作系统中,有很多进程,当数据到来时要提交给哪个进程进行处理呢?这就需要用到端口号。在TCP头中,有源端口号(Source Port)和目标端口号(Destination Port)。源端口号标识了发送主机的进程,目标端口号标识接受方主机的进程。

(2)序号[32bit]

序号分为发送序号(Sequence Number)和确认序号(Acknowledgment Number)。

发送序号:用来标识从 TCP源端向 TCP目的端发送的数据字节流,它表示在这个报文段中的第一个数据字节的顺序号。如果将字节流看作在两个应用程序间的单向流动,则 TCP用顺序号对每个字节进行计数。序号是 32bit的无符号数,序号到达 2  32- 1后又从 0开始。当建立一个新的连接时, SYN标志变 1,顺序号字段包含由这个主机选择的该连接的初始顺序号 ISN( Initial Sequence Number)。

确认序号:包含发送确认的一端所期望收到的下一个顺序号。因此,确认序号应当是上次已成功收到数据字节顺序号加 1。只有 ACK标志为 1时确认序号字段才有效。 TCP为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据顺序号。

(3)偏移[4bit]

这里的偏移实际指的是TCP首部的长度,它用来表明TCP首部中32 bit字的数目,通过它可以知道一个TCP包它的用户数据是从哪里开始的。这个字段占4bit,如4bit的值是0101,则说明TCP首部长度是5 * 4 = 20字节。 所以TCP的首部长度最大为15 * 4 = 60字节。然而没有可选字段,正常长度为20字节。

(4)Reserved [6bit]

目前没有使用,它的值都为0

(5)标志[6bit]

在TCP首部中有6个标志比特。他们中的多个可同时被置为1 。

URG         紧急指针(urgent pointer)有效
ACK          确认序号有效
PSH          指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满

RST           一般表示断开一个连接
例如:一个TCP的客户端向一个没有监听的端口的服务器端发起连接,wirshark抓包如下



可以看到host:192.168.63.134向host:192.168.63.132发起连接请求,但是host:192.168.63.132并没有处于监听对应端口的服务器端,这时
host : 192.168.63.132发一个RST置位的TCP包断开连接。

SYN          同步序号用来发起一个连接
FIN            发送端完成发送任务(即断开连接)

(6)窗口大小(window)[16bit]

窗口的大小,表示源方法最多能接受的字节数。。

(7)校验和[16bit]

校验和覆盖了整个的TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。

(8)紧急指针[16bit]

只有当URG标志置为1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。

三次握手:



TCP通信流程框图


SOCKE具体实现

客户端:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
	SOCKET clientsocket;
	SOCKADDR_IN serveraddr;
	SOCKADDR_IN clientaddr;
	char buf[1024];

    WSADATA wsa;
	WSAStartup(MAKEWORD(2,0),&wsa);	//初始化WS2_32.DLL
    
	//创建套接字
	if((clientsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <= 0)
	{
		printf("套接字socket创建失败!\n");
		return -1;
	}

	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(9102);
	serveraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    
	//请求连接
	printf("尝试连接中...\n");
	if(connect(clientsocket, (SOCKADDR *)&serveraddr, sizeof(serveraddr)) != 0)
	{
		printf("连接失败!\n");
		return -1;
	}
	printf("连接成功!\n");
	
	//发送数据
	printf("请输入发送给服务器的字符:\n");
	scanf("%s", buf);
	if(send(clientsocket, buf, strlen(buf)+1, 0)<=0)
	{
		printf("发送错误!\n");
	}
    
	//接收数据
	while(1){
		if(recv(clientsocket, buf, 1024, 0) <= 0)	
		{
			printf("关闭连接!\n");
			closesocket(clientsocket);
		}
		printf("接收来自服务器的信息: %s\n",buf);
        break;
	}
    //关闭套接字
    closesocket(clientsocket);
	WSACleanup();    //释放WS2_32.DLL
	return 0;
}

服务端:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
	SOCKET serversoc;   
	SOCKET clientsoc;
	SOCKADDR_IN serveraddr;
	SOCKADDR_IN clientaddr;
	char buf[1024];
	int len;

    WSADATA wsa;
	WSAStartup(MAKEWORD(2,0),&wsa);	//初始化WS2_32.DLL

	//创建套接字
	if((serversoc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <= 0)  
	{
		printf("套接字socket创建失败!\n");
		return -1;
	}
	
	//命名协议,IP,端口
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(9102);
	serveraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    
	//绑定套接字
	if(bind(serversoc, (SOCKADDR *)&serveraddr, sizeof(serveraddr)) != 0)
	{
		printf("套接字绑定失败!\n");
		return -1;
	}
	
	printf("开始监听...\n");
	//监听请求
	if(listen(serversoc, 1) != 0)
	{
		printf("监听失败!\n");
		return -1;
	}

	len = sizeof(SOCKADDR_IN);
	
	//接收请求
	if((clientsoc = accept(serversoc, (SOCKADDR *)&clientaddr, &len))<=0)
	{
		printf("接受连接失败!\n");
		return -1;
	}
	printf("连接成功\n");

	//接收数据
	while(1)
	{
		if(recv(clientsoc, buf, 1024, 0) <= 0)	
		{
			printf("关闭连接!\n");
			closesocket(clientsoc);
		}
		printf("接收来自客户端的信息: %s\n",buf);
		break;
	}

	//发送数据
	printf("请输入发送给客户端的字符:\n");
	scanf("%s", buf);
	//send to client
	if(send(clientsoc, buf, strlen(buf)+1, 0)<=0)
	{
		printf("发送错误!\n");
	}
	WSACleanup();     //释放WS2_32.DLL
	return 0;
}


参考文章

http://blog.youkuaiyun.com/lovecodeless/article/details/25061141

http://blog.youkuaiyun.com/mao834099514/article/details/52763490?locationNum=2&fps=1

http://blog.chinaunix.net/uid-26833883-id-3627644.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值