Day2: Windows network programming - TCP

I started to learn Windows network programming today, and I always fell into the complicated parameters of Windows when I was learning, and I was entangled in these. From the teacher's explanation, these contents belong to the fixed formula, mainly learning the logic of writing. Remind yourself, put your energy in the right place, and don't waste energy for no reason.

About C/S mode

C: Client client:

  • Open a communication channel and connect to a specific port on the server host
  • Send a service request to the server, wait for and receive the response, and continue to make the request
  • Close the communication channel and terminate after the request ends

S: Server server:

  • First, the server starts first and provides corresponding services according to the request
  • Open a communication channel to receive requests on a certain address and port
  • Wait for client requests to arrive at this port
  • A service request is received, the request is processed and an acknowledgment signal is sent
  • Return to step 2, waiting for another client request
  • shutdown server

About TCP and UDP

TCP: connection-oriented sockets

  • Data will not be lost during transfer
  • transfer data sequentially
  • There is no data boundary in the process of transmission

UDP: message-oriented sockets

  • Emphasize fast transfers rather than sequential
  • Data transmitted may be lost or corrupted
  • Limit the size of data transferred each time
  • The transmitted data is bounded by the data boundary

About IP

It is an Internet protocol assigned to users to surf the Internet

Common IP addresses are divided into two categories: IPv4 and IPv6

About ports

The sequence number assigned to sockets to distinguish sockets created in the program

Different ports correspond to different applications

There are as many as 65536 ports

About socket type and protocol settings

SOCK_STREAM corresponds to the TCP protocol

SOCK_DGRAM corresponds to UDP

SOCK_RAW can read and write IP packets that are not processed by the kernel, avoiding the TCP/IP processing mechanism, and the transmitted datagram can be directly transmitted to the application program that needs it

Basic functions and basic data structures about network programming

Two important data structures:

struct sockaddr {
    u_short sa_family; //16 位地址类型 2 字节
    char sa_data[14]; //14 字节地址数据:ip + port
};
struct sockaddr_in {
    short sin_family; //16 位地址类型
    u_short sin_port; //16 位端口号 65535 2 的 16 次方
    struct in_addr sin_addr; //32 位 IP 地址 4 字节
    char sin_zero[8]; //8 字节填充
};

TCP socket

TCP Server

code show as below:

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

// error LNK2019: 无法解析的外部符号 __imp__accept@12,函数 _main 中引用了该符号
#pragma comment(lib,"ws2_32.lib")   // 导入库

int main()
{
	printf("TCP Server\n");
	// 0. 初始化网络库 加载套接字库 这是个标准做法,直接拷贝即可
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}

	// 1. 安装电话机
	// AF_INET指定了使用 IPv4 地址和 TCP/IP 协议栈进行网络通信。
	// 用于指定套接字的类型,表示基于流的传输协议,常用于 TCP 协议的网络通信。
	// 第三个参数由前两个决定。
	// 光标放在socket()函数上,摁下F1,转到官方文档
	SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);   //socket 函数创建一个绑定到特定传输服务提供者的套接字。
	if (INVALID_SOCKET == sockSrv)
	{
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	
	// 2. 分配电话号码,填充参数
	SOCKADDR_IN addSrv;  // SOCKADDR_IN结构指定 AF_INET 地址系列的传输地址和端口。
	// 设置为任何IP
	addSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addSrv.sin_family = AF_INET;
	addSrv.sin_port = htons(6000);
	// 根据官方文档,这里需要强转 
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR))) // 绑定函数将本地地址与套接字相关联。
	{
		printf("bind errorNum = %d\n", GetLastError());
		return -1;
	}
	
	// 3. 监听 listen
	// 将套接字置于侦听传入连接的状态。
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
		printf("listen errorNum = %d\n", GetLastError());
		return -1;
	}

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);
	while (true)
	{
		// 4.分配一台分机去处理客户端的连接
		printf("begin accept: \n");
		SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrCli,&len);
		printf("end accept: \n");
		char sendBuf[100] = { 0 };
		sprintf_s(sendBuf, 100, "Welcome %s to bingo!",inet_ntoa(addrCli.sin_addr));
		int iLen = send(sockConn,sendBuf,strlen(sendBuf),0);
		char recvBuf[100] = { 0 };
		iLen = recv(sockConn, recvBuf, 100, 0);
		printf("recvBuf = %s\n", recvBuf);
		// 关闭分机
		closesocket(sockConn);
	}
	// 关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

TCP Client

code show as below:

#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	printf("Client\n");
	char sendBuf[] = "hello,world";
	//1 初始化网络库
	// 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	// 新建套接字
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("10.134.142.81");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	// 3 连接服务器
	if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		printf("connect errorNum = %d\n", GetLastError());
		return -1;
	}
	// 4 接收和发送数据
	char recvBuf[100] = { 0 };
	int iLen = recv(sockCli, recvBuf, 100, 0);
	if (iLen < 0)
	{
		printf("recv errorNum = %d\n", GetLastError());
		return -1;
	}
	printf("Client recvBuf = %s\n", recvBuf);
	// 发送数据
	iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
	if (iLen < 0)
	{
		printf("send errorNum = %d\n", GetLastError());
		return -1;
	}
	// 关闭套接字
	closesocket(sockCli);
	WSACleanup();
	system("pause");
	return 0;
}

// 报错:严重性	代码	说明	项目	文件	行	禁止显示状态
// 错误	C4996	'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings	TCP Client	C : \Users\mi\Desktop\Code - C++\MyNetPro\TCP Client\TCP Client.cpp	38
// 我们需要把它 _WINSOCK_DEPRECATED 忽略掉 工程属性 C++预处理器添加即可

The result is as follows:

About Listen()

listen(sockSrv,5): This function specifies the message queue, there can be up to five, and the message that comes later will fail to connect.

About the startup items of VS

Which one is set as the startup item will compile the C++ file of that process

About error: _WINSOCK_DEPRECATED 

// Error: Severity Code Description Item File Line Forbidden Status
// Error C4996 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings TCP Client C : \Users\mi\Desktop\ Code - C++\MyNetPro\TCP Client\TCP Client.cpp 38
// We need to ignore its _WINSOCK_DEPRECATED project property C++ preprocessor and add it

Optimize recv and send to solve the problem of large data transmission

Receiving end:

int MySocketRecv0(int sock, char* buf, int dateSize)
{
	//循环接收
	int numsRecvSoFar = 0;        // 目前受到的数据
	int numsRemainingToRecv = dateSize; // 剩余要收的数据
	printf("enter MySocketRecv0\n");
	while (1)
	{
		int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
		printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesRead, numsRecvSoFar, numsRemainingToRecv);
		if (bytesRead == numsRemainingToRecv) // 一次性完全接收
		{
			return 0;
		}
		else if (bytesRead > 0) // 未接收完,更新已接受数据和未接受数据
		{
			numsRecvSoFar += bytesRead;
			numsRemainingToRecv -= bytesRead;
			continue;
		}
		else if ((bytesRead < 0) && (errno == EAGAIN))
		{
			continue;
		}
		else
		{
			return -1;
		}
	}
}

sender:

int MySocketSend0(int socketNum, unsigned char* data, unsigned dataSize)
{
	unsigned numBytesSentSoFar = 0;
	unsigned numBytesRemainingToSend = dataSize;
	while (1)
	{
		int bytesSend = send(socketNum, (char const*)(&data[numBytesSentSoFar]),
			numBytesRemainingToSend, 0/*flags*/);
		if (bytesSend == numBytesRemainingToSend)
		{
			return 0;
		}
		else if (bytesSend > 0)
		{
			numBytesSentSoFar += bytesSend;
			numBytesRemainingToSend -= bytesSend;
			continue;
		}
		else if ((bytesSend < 0) && (errno == 11))
		{
			continue;
		}
		else
		{
			return -1;
		}
	}
}

Guess you like

Origin blog.csdn.net/qq_61553520/article/details/130906188