Socket编程之TCP实例(附C/C++代码详解)

    说明:主要分步骤给出Windows平台下socket编程的一个TCP实例;使用WINDOWS下网络编程规范Winsock完成网络通信;对程序各部分细节进行描述。

套接字有三种传输类型SOCK_STREAM SOCK_DGRAM SOCK_RAW;
具体见:https://blog.csdn.net/bjyddxhfxq/article/details/51119653

源码完整版下载:https://download.csdn.net/download/ckzhb/10601058

一、服务器

    功能:监控端口,等待客户端的请求;建立连接成功后,服务器每输入一次数据,发送一组数据;若输入 q,则停止发送。

1、加载套接字库,创建套接字。

#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")   //静态加入一个lib文件
    
    WORD sockVersion = MAKEWORD(2, 2);  	
	WSADATA wsaData;   	
	if (WSAStartup(sockVersion, &wsaData) != 0) //WSAStartup返回0表示设置初始化成功
		return 0; 
	
	/*创建套接字*/
	//AF_INET表示IPv4,SOCK_STREAM数据传输方式,IPPROTO_TCP传输协议;
	SOCKET listenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (listenSocket == INVALID_SOCKET)
	{
		printf("套接字创建失败");
		WSACleanup();
		return 0;
	}

说明:

  1. WORD是微软SDK中的无符号16位整形数;WSADATA是一个结构体;
  2. MAKEWORD(a,b)是一个宏,这里用来指定使用的Winsock版本;
  3. WSAStartup,即WSA(Windows Sockets Asynchronous,Windows异步套接字)的启动命令;WSAStartup必须是应用程序或DLL调用的第一个Windows Sockets函数。它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
  4. 函数socket(),socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源;若无错误发生,socket()返回引用新套接口的描述字。否则的话,返回INVALID_SOCKET错误。

2、绑定套接字到一个IP地址和一个端口上

/*绑定IP和端口*/
	//配置监听地址和端口
	sockaddr_in addrListen;
	addrListen.sin_family = AF_INET;     //指定IP格式
	addrListen.sin_port = htons(8888);   //绑定端口号
	addrListen.sin_addr.S_un.S_addr = INADDR_ANY;  //表示任何IP   service.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(listenSocket, (SOCKADDR*)&addrListen, sizeof(addrListen)) == SOCKET_ERROR)  //(SOCKADDR*)
	{
		printf("绑定失败");
		closesocket(listenSocket);
		return 0;
	}	

说明:

  1. sockaddr_in是一个数据结构;用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
  2. bind()函数int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);

bind()函数通过给一个套接字接口分配一个地址来建立捆绑。

3、监听指定端口

/*开始监听*/
	if (listen(listenSocket, 5) == SOCKET_ERROR)
	{
		printf("监听出错");
		closesocket(listenSocket);
		return 0;
	}

说明:

  • int listen( int sockfd, int backlog);
  • sockfd:用于标识一个已捆绑未连接套接口的描述字。
  • backlog:等待连接队列的最大长度。

4、等待客户端请求,若收到请求,建立一个对应于此次连接的套接字

/*等待连接,连接后建立一个新的套接字*/
	SOCKET revSocket;  //对应此时所建立连接的套接字的句柄
	sockaddr_in remoteAddr;   //接收连接到服务器上的地址信息
	int remoteAddrLen = sizeof(remoteAddr);	
	printf("等待连接...\n");
	/*等待客户端请求,服务器接收请求*/
	revSocket = accept(listenSocket, (SOCKADDR*)&remoteAddr, &remoteAddrLen);  //等待客户端接入,直到有客户端连接上来为止
	if (revSocket == INVALID_SOCKET)
	{
		printf("客户端发出请求,服务器接收请求失败:\n",WSAGetLastError());
		closesocket(listenSocket);
		WSACleanup();
		return 0;
	}
	else
	{
		printf("客服端与服务器建立连接成功:%s \n", inet_ntoa(remoteAddr.sin_addr));
	}

说明:

  1. 首先定义一个新的套接字,注意该套接字是与之前的不同,数据传输时,使用本套接字。
  2. SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

函数从等待连接队列中抽取第一个连接,创建一个同类的新的套接口并返回句柄;

  • sockfd:套接字描述符,该套接口在listen()后监听连接;
  • addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
  • addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。

5、接收客户端数据

char revData[255] = "";
	char *sendData = new char[100];
	/*通过建立的连接进行通信*/
	int res = recv(revSocket, revData, 255, 0);
	if (res > 0)
	{
		printf("Bytes received: %d\n", res);
		printf("客户端发送的数据: %s\n", revData);
	}
	else if (res == 0)
		printf("Connection closed\n");
	else
		printf("recv failed: %d\n", WSAGetLastError());

说明:

     int recv( SOCKET s, char FAR *buf, int len, int flags );

不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据,根据返回值判断数据接收情况。

  • s:    指定接收端套接字描述符;
  • buf: 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
  • len: 指明buf的长度;
  • flag:s一般为0;

6、向客户端发送数据

while (cin>>sendData)
	{
		//cout << strlen(sendData) << endl;
		if (strcmp(sendData, "q") == 0)
		{
			printf("服务器停止发送数据!\n");
			break;
		}		
		//发送数据
		send(revSocket, sendData, strlen(sendData), 0);
	}

说明:

    int send( SOCKET s, const char FAR *buf, int len, int flags );

含义基本同recv()函数。

7、关闭套接字,关闭加载的套接字库

closesocket(listenSocket);
	WSACleanup();

二、客户端

    部分函数与服务器端相同,不再单独列出!

1、加载套接字库,创建套接字

WORD sockVerson = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVerson, &wsaData) != 0)
		return 0;
	
	//建立客户端socket
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == INVALID_SOCKET)
	{
		printf("套接字创建失败");
		WSACleanup();
		return 0;
	}

2、向服务器发出连接请求

//定义要连接的服务器地址
	sockaddr_in addrConServer;
	addrConServer.sin_family = AF_INET;
	addrConServer.sin_port = htons(8888);
	addrConServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (connect(clientSocket, (SOCKADDR*)&addrConServer, sizeof(addrConServer)) == SOCKET_ERROR)
	{
		printf("客户端建立连接失败!\n");
		closesocket(clientSocket);
		WSACleanup();
		return 0;
	}
	else 
		printf("客户端建立连接成功,准备发送数据!\n");

说明:

    int connect(SOCKET s, const struct sockaddr * name, int namelen);

    本函数用于创建与指定外部端口的连接,对于流类套接口(SOCK_STREAM类型),利用名字来与一个远程主机建立连接,一旦套接口调用成功返回,它就能收发数据了。对于数据报类套接口(SOCK_DGRAM类型),则设置成一个缺省的目的地址,并用它来进行后续的send()与recv()调用。

3、与服务器之间进行数据传输

//发送数据
	int sendRes = send(clientSocket, sendBuf, (int)strlen(sendBuf), 0);
	if (sendRes == SOCKET_ERROR)
	{
		printf("客户端send()出现错误 : %d\n", WSAGetLastError());
		closesocket(clientSocket);
		WSACleanup();
		return 0;
	}
	else
		printf("客户端发送数据成功!\n");
 
	//接收服务端数据
	/*通过建立的连接进行通信*/
	do
	{	
		char revSerData[100] = "";
		res = recv(clientSocket, revSerData, sizeof(revSerData), 0);
		if (res > 0)
		{
			printf("Bytes received: %d\n", res);
			printf("服务器发送的数据: %s\n", revSerData);
		}
		else if (res == 0)
			printf("Connection closed\n");
		else
			printf("recv failed: %d\n", WSAGetLastError());
	} while (res > 0);

4、关闭套接字,关闭加载的套接字库

closesocket(clientSocket);
	WSACleanup();

三、运行结果

    先运行服务器,后运行客户端;

    然后在服务器中输入拟发送的数据;

1、服务器运行初始界面

在这里插入图片描述

2、打开客户端后的初始界面

在这里插入图片描述

3、在服务器中输入数据

在这里插入图片描述

发布了25 篇原创文章 · 获赞 7 · 访问量 2140

猜你喜欢

转载自blog.csdn.net/qq_41506111/article/details/102779803