C/C++ Socket - TCP 与 UDP 网络编程

Posted cpp_learners

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++ Socket - TCP 与 UDP 网络编程相关的知识,希望对你有一定的参考价值。

前言

socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。

如下图:

头文件与库:

#include <WinSock2.h>

#pragma comment(lib, "ws2_32.lib")

准备工作:

创建工程后,首先右键工程,选择属性

然后选择 C/C++ - 预处理器 - 预处理器定义

将字符串 _WINSOCK_DEPRECATED_NO_WARNINGS 添加到里面去,点击应用即可!


TCP

连接过程图:

创建tcp服务器和客户端都是按照上图的步骤来操作的!

1). 服务器

  1. 初始化套接字库
    对应图中socket()

    WORD wVersion;
    WSADATA wsaData;
    int err;
    
    // 设置版本,可以理解为1.1
    wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来
    
    // 启动
    err = WSAStartup(wVersion, &wsaData);
    
  2. 创建tcp套接字
    对应图中socket()

    // AF_INET:ipv4   AF_INET6:ipv6
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
    
  3. 绑定到本机
    对应图中bind()

    // 准备绑定信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);	// 设置绑定网卡
    addrSrv.sin_family = AF_INET;		// 设置绑定网络模式
    addrSrv.sin_port = htons(6000);		// 设置绑定端口
    // hton: host to network  x86:小端    网络传输:htons大端
    
    // 绑定到本机
    int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
    
  4. 监听
    对应图中listen()

    // 同时能接收10个链接,主要看参数二的设置个数
    listen(sockSrv, 10);
    
  5. 接收连接请求,返回针对客户端的套接字
    对应图中accept()

    SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
    
  6. 发送数据
    对应图中write()

    sprintf_s(sendBuf, "hello client!\\n");
    int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
    
  7. 接收数据
    对应图中read()

    recv(sockConn, recvBuf, 100, 0);
    
  8. 关闭套接字
    对应图中close()

    closesocket(sockConn);
    
  9. 清理套接字库

    WSACleanup();
    

具体实现代码

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

#pragma comment(lib, "ws2_32.lib")


int main(void) 

	// 1.初始化套接字库
	WORD wVersion;
	WSADATA wsaData;
	int err;

	// 设置版本,可以理解为1.1
	wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来

	// 启动
	err = WSAStartup(wVersion, &wsaData);
	if (err != 0) 
		return err;
	
	// 检查:网络低位不等于1 || 网络高位不等于1
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) 
		// 清理套接字库
		WSACleanup();
		return -1;
	

	// 2.创建tcp套接字		// AF_INET:ipv4   AF_INET6:ipv6
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	// 准备绑定信息
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);	// 设置绑定网卡
	addrSrv.sin_family = AF_INET;		// 设置绑定网络模式
	addrSrv.sin_port = htons(6000);		// 设置绑定端口
	// hton: host to network  x86:小端    网络传输:htons大端

	// 3.绑定到本机
	int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
	if (retVal == SOCKET_ERROR) 
		printf("Failed bind:%d\\n", WSAGetLastError());
		return -1;
	

	// 4.监听,同时能接收10个链接
	if (listen(sockSrv, 10) == SOCKET_ERROR) 
		printf("Listen failed:%d", WSAGetLastError());
		return -1;
	

	std::cout << "Server start at port: 6000" << std::endl;

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	char recvBuf[100];
	char sendBuf[100];
	while (1) 
		// 5.接收连接请求,返回针对客户端的套接字
		SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
		if (sockConn == SOCKET_ERROR) 
			//printf("Accept failed:%d", WSAGetLastError());
			std::cout << "Accept failed: " << WSAGetLastError() << std::endl;
			break;
		

		//printf("Accept client IP:[%s]\\n", inet_ntoa(addrCli.sin_addr));
		std::cout << "Accept client IP: " << inet_ntoa(addrCli.sin_addr) << std::endl;

		// 6.发送数据
		sprintf_s(sendBuf, "hello client!\\n");
		int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		if (iSend == SOCKET_ERROR) 
			std::cout << "send failed!\\n";
			break;
		

		// 7.接收数据
		recv(sockConn, recvBuf, 100, 0);
		std::cout << recvBuf << std::endl;

		// 关闭套接字
		closesocket(sockConn);
	


	// 8.关闭套接字
	closesocket(sockSrv);

	// 9.清理套接字库
	WSACleanup();

	return 0;

2). 客户端

  1. 初始化套接字库
    对应图中socket()

    WORD wVersion;
    WSADATA wsaData;
    int err;
    
    // 可以理解为1.1
    wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来
    
    // 启动
    err = WSAStartup(wVersion, &wsaData);
    
    // 创建TCP套接字
    SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
    
  2. 连接服务器
    对应图中connect()

    // 连接服务器
    int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
    
  3. 发送数据到服务器
    对应图中write()

    char sendBuf[] = "你好,服务器,我是客户端!";
    send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
    
  4. 接收服务器的数据
    对应图中read()

    char recvBuf[100];
    recv(sockCli, recvBuf, sizeof(recvBuf), 0);
    
  5. 关闭套接字并清除套接字库
    对应图中close()

    closesocket(sockCli);
    WSACleanup();
    

具体实现代码

#include <iostream>
#include <WinSock2.h>


#pragma comment(lib, "ws2_32.lib")


int main(void) 

	// 1.初始化套接字库
	WORD wVersion;
	WSADATA wsaData;
	int err;

	// 可以理解为1.1
	wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来

	// 启动
	err = WSAStartup(wVersion, &wsaData);
	if (err != 0) 
		return err;
	
	// 检查:网络地位不等于1 || 网络高位不等于1
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) 
		// 清理套接字库
		WSACleanup();
		return -1;
	

	// 创建TCP套接字
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");	// 服务器地址
	addrSrv.sin_port = htons(6000);		// 端口号
	addrSrv.sin_family = AF_INET;		// 地址类型(ipv4)

	// 2.连接服务器
	int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
	if (err_log == 0) 
		printf("连接服务器成功!\\n");
	
	 else 
		printf("连接服务器失败!\\n");
		return -1;
	


	char recvBuf[100];
	char sendBuf[] = "你好,服务器,我是客户端!";
	// 3.发送数据到服务器
	send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);

	// 4.接收服务器的数据
	recv(sockCli, recvBuf, sizeof(recvBuf), 0);
	std::cout << recvBuf << std::endl;


	// 5.关闭套接字并清除套接字库
	closesocket(sockCli);
	WSACleanup();

	system("pause");

	return 0;

运行效果:

3). TCP聊天小项目

下面是根据上面的代码修改的一个聊天小项目(使用到了多线程)

只有一个服务器,服务器一直开启等待客户端连接;
客户都安可以开启多个,且可以一直连续的与服务器进行发送接收消息;
服务器给客户端发送数据,得通过1 - 9来区分到底给那个客户端发送消息,例如给第二个客户端发送消息:2你好,客户端
客户端那边接收到的数据是:你好,客户端

服务器代码

#include <iostream>
#include <WinSock2.h>
#include <stdio.h>
#include <Windows.h>
#include <process.h>
#include <vector>
#include <conio.h>
#include <string.h>
#include <string>

#pragma comment(lib, "ws2_32.lib")


SOCKET sockSrv;
std::vector<SOCKET> vec_sockConn;
std::vector<SOCKADDR_IN> vec_sockaddr_in;
std::vector<int> vec_sockIndex;


// 这个结构体用作线程参数
typedef struct SERVER_CLIENT 
	SOCKET server;
	SOCKADDR_IN client;
	int clientIndex;
SC;



// 判断有没有断开连接
bool IsSocketClosed(SOCKET clientSocket) 
	bool ret = false;
	HANDLE closeEvent = WSACreateEvent();
	WSAEventSelect(clientSocket, closeEvent, FD_CLOSE);

	DWORD dwRet = WaitForSingleObject(closeEvent, 0);

	if (dwRet == WSA_WAIT_EVENT_0)
		ret = true;
	else if (dwRet == WSA_WAIT_TIMEOUT)
		ret = false;

	WSACloseEvent(closeEvent);
	return ret;



// 接收请求
unsigned int WINAPI  ThreadAccept(LPVOID p) 
	static int i = 0;
	while (1) 
		SOCKADDR_IN addrCli;
		int len = sizeof(SOCKADDR);

		// 5.接收连接请求,返回针对客户端的套接字
		SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
		if (sockConn == SOCKET_ERROR) 
			printf("Accept failed:%d", WSAGetLastError());
		

		// 存储当前服务器与客户端 连接绑定的socket
		vec_sockIndex.emplace_back(i++);
		vec_sockaddr_in.emplace_back(addrCli);
		vec_sockConn.emplace_back(sockConn);

		printf("\\033[0;%d;40m客户端[%d]上线\\033[0m\\n", 31, i);
	
	

	return 0;


unsigned int WINAPI  _ThreadRecv(LPVOID p) 
	char recvBuf[100];
	memset(recvBuf, 0, 100);

	SC _sc = *(SC *)p;
	
	while (1) 
		Sleep(20);

		if (IsSocketClosed(_sc.server) == true) 
			printf("客户端 [%d] 断开连接!\\n", _sc.clientIndex + 1);
			break;
		


		// 接收数据
		recv(_sc.server, recvBuf, 100, 0);
		if (strlen(recvBuf) == 0) 
			continue;
		
	
		printf("接收到客户端 [%d] 的消息:%s\\n", _sc.clientIndex + 1, recvBuf);
		memset(recvBuf, 0, 100);
	
	return 0;


unsigned int WINAPI  ThreadRecv(LPVOID p) 

	static int index = 0;

	while (1) 
		// 还没有客户端与服务器进行连接
		if (vec_sockConn.size() == 0) 
			continue;
		

		// 接收线程已经开启和客户端个数相等
		if (vec_sockConn.size()  == index) 
			continue;
		

		SC sc;
		sc.server = vec_sockConn.at(index);
		sc.client = vec_sockaddr_in.at(index);
		sc.clientIndex = vec_sockIndex.at(index);

		HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, _ThreadRecv, (void *)&sc, 0, NULL);

		index++;
		Sleep(20);
	

	return 0;




int main(void) 

	// 1.初始化套接字库
	WORD wVersion;
	WSADATA wsaData;
	int err;

	// 设置版本,可以理解为1.1
	wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来

	// 启动
	err = WSAStartup(wVersion, &wsaData);
	if (err != 0) 
		return err;
	
	// 检查:网络低位不等于1 || 网络高位不等于1
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) 
		// 清理套接字库
		WSACleanup();
		return -1;
	

	// 2.创建tcp套接字		// AF_INET:ipv4   AF_INET6:ipv6
	sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	// 准备绑定信息
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);	// 设置绑定网卡
	addrSrv.sin_family = AF_INET;		// 设置绑定网络模式
	addrSrv.sin_port = htons(6000);		// 设置绑定端口
	// hton: host to network  x86:小端    网络传输:htons大端

	// 3.绑定到本机
	int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
	if (retVal == SOCKET_ERROR) 
		printf("Failed bind:%d\\n", WSAGetLastError());
		return -1;
	

	// 4.监听,同时接收10个链接
	if (listen(sockSrv, 10) == SOCKET_ERROR) 
		printf("Listen failed:%d", WSAGetLastError以上是关于C/C++ Socket - TCP 与 UDP 网络编程的主要内容,如果未能解决你的问题,请参考以下文章

C语言 UDP socket 简单客户端 编程,急

HP-Socket 高性能 TCP & UDP 通信框架

高性能 TCP/UDP/HTTP 通信框架 HP-Socket v4.1.2

高性能 TCP/UDP/HTTP 通信框架 HP-Socket v4.0.1 发布

高性能 TCP &amp; UDP 通信框架 HP-Socket v3.2.2 正式公布

高性能 TCP & UDP 通信框架 HP-Socket v3.5.1 正式发布