【C++笔记】Socket 通信相关知识总结( 附c++完整代码 )

1 综述

因工作中用到Socket通信,基于网上已有大量相关Socket文章介绍,但提供的代码都较为简单,因此本文主要简单介绍一下Socket原理和Socket类中相关函数,并对网上现有的C++实现TCP/IP通信代码进行了部分改善,附在本文末尾,希望可以帮助入门者,如需更深入的功能,自行扩展。

参考博客:
Socket通信原理探讨(C++为例)
C++ socket通讯详解及注意事项

1.1 socket原理

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。因此socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

1.2 Socket库基本函数

(1)socket() 函数

int socket(int domain, int type, int protocol);

socket() 函数用于创建一个socket的套接字,它唯一标识一个socket。后续的操作都有用到这个套接字,把它作为参数,通过它来进行一些读写操作。

domain参数:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL…

type参数:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET等等

protocol参数:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。当protocol为0时,会自动选择type类型对应的默认协议。

使用:int server_socket = socket(PF_INET, SOCK_STREAM, 0);—返回socket套接字

(2) bind() 函数

int bind( int sockfd, const struct sockaddr addr, socklen_t addrlen);

bind() 函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

sockfd参数:即socket套接字,它是通过socket()函数创建了,唯一标识一个socket.
addr参数:一个const struct sockaddr 指针,指向要绑定给sockfd的协议地址。
addrlen参数:对应的是地址的长度。
使用:bind(server_socket, (struct sockaddr
)&server_addr, sizeof(server_addr))—返回0即表示成功

(3)listen()、connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

服务器:int listen(int sockfd, int backlog);
客户端:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket套接字,第二个参数为相应socket可以排队的最大连接个数。

connect函数的第一个参数即为客户端的socket套接字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。

(4)accept() 函数

**int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。

accept函数返回的是已连接的socket套接字。

(5)read()、write() 等函数

调用网络I/O进行读写操作,即实现了网络中不同进程之间的通信,网络I/O操作有下面几组:

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
开发语言不同可能读写函数也就不同,只要把自己想要发送的消息,以字节流的方式写入Socket或者从Socket读出来即可实现网络的I/O操作;

(6)close()函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用close关闭打开的文件

1.3 Socket类中 bind() 和 std::bind() 冲突问题

Socket中有一个bind函数, 原型如下 :

// socket.h
int	bind(int, const struct sockaddr *, socklen_t)

该函数是绑定Socket,而在C++11的 thread 库中增加了std::bind(_Fp &&__f, _BoundArgs &&__bound_args…) 函数,可以用该函数绑定函数指针,当在C++类中添加了using namespace std; 之后,调用socket.h中的 bind 方法则会出现问题,使得bind函数调用不是你想要的行为。

解决方案:

1、在使用 socket.h 的 bind() 函数的类中不要使用using namespace std,即通过namespace来回避同名函数问题;

2、在创建多线程时,CreateThread() 是用的Windows API来创建的线程,而thread库是c++的线程库,可以跨平台。因此当在win32平台下,可以回避 thread 库里的std::bind()函数;

1.4 c++ 代码

(1)多个client端可同时访问server端,每个client端对应一个独立线程,若涉及到共享数据时,可使用std::lock_guard< std::mutex > m_locker(mtx) 来进行加锁保护;

(2)因代码运行在同台电脑上,IP地址设为127.0.0.1;

server端代码如下:

// Summary: Socket通信;
// Author:  jryang
// email:   muyijames@126.com
// Date:    2020-09-10

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<thread>
#include<winsock2.h>                  //引用头文件
#pragma comment(lib,"ws2_32.lib")     //链接库文件
//using namespace std;
char Ip[20][200] = { '\0' };
int iConnect = 0;                     //当前客户端数量

std::string function1(){
	std::cout << "running function1..." << std::endl;
	_sleep(5);
	std::string temp;
	temp = "function1 call back...";
	return temp;
}

std::string function2(){
	std::cout << "running function1..." << std::endl;
	std::string temp;
	temp = "function2 call back...";
	return temp;
}

//使用thread库时,不要使用namespace std,因为std::bind()有冲突;
void test_threadpro(void* pParam)           //创建多线程函数;
{
	std::cout << "a new client connected..." << std::endl;
	SOCKET hsock = (SOCKET)pParam;
	char buffer[1024];
	char sendBuffer[1024];
	if (hsock != INVALID_SOCKET)                //INVALID_SOCKET表示无效
		std::cout << "Start receive information from IP:" << Ip[iConnect] << std::endl << std::endl;
	while (true){
		int num = recv(hsock, buffer, 1024, 0);                   //阻塞函数,等待接受内容
		if (num <= 0){
			std::cout << "Client with IP:" << Ip[iConnect] << " disconnected!" << std::endl;
			break;
		}
		if (num >= 0)
			std::cout << "Information from:" << Ip[iConnect] << ":" << buffer << std::endl;
		if (!strcmp(buffer, "function1")){
			memset(sendBuffer, 0, 1024);
			std::string temp;
			temp = function1();
			strcpy(sendBuffer, temp.c_str());
			std::cout << "sleep 5s..." << std::endl;
			_sleep(5000);
			int ires = send(hsock, sendBuffer, sizeof(sendBuffer), 0);         //回送信息
			std::cout << "The message sent to IP:" << Ip[iConnect] << "is: " << sendBuffer << std::endl;
		}
		else if (!strcmp(buffer, "function2")){
			memset(sendBuffer, 0, 1024);
			std::string temp;
			temp = function2();
			strcpy(sendBuffer, temp.c_str());
			int ires = send(hsock, sendBuffer, sizeof(sendBuffer), 0);         //回送信息
			std::cout << "The message sent to IP:" << Ip[iConnect] << "is: " << sendBuffer << std::endl;
		}
		else if (!strcmp(buffer, "exit")){
			//如果接受到 exit 结束该进程
			std::cout << "Client with IP:" << Ip[iConnect] << " disconnected!" << std::endl;
			std::cout << "Server Process Close:  " << std::endl;
			return;
		}
		else{
			memset(sendBuffer, 0, 1024);
			strcpy(sendBuffer, "Waiting for development...");
			int ires = send(hsock, sendBuffer, sizeof(sendBuffer), 0);
			std::cout << "The message sent to IP:" << Ip[iConnect] << "is: " << sendBuffer << std::endl;
		}
	}
	return;
};

int main(void)
{
	WSADATA wsd;         //定义WSADATA对象
	if (WSAStartup(MAKEWORD(2, 2), &wsd)){
		printf("Initlalization Error!");
		return -1;
	}

	SOCKET m_SockServer;            //创建socket对象           
	sockaddr_in serveraddr;         //创建sockaddr_in对象储存自身信息(当有多个端口,可以多个绑定)
	sockaddr_in serveraddrfrom;
	SOCKET m_Server[20];            //创建socket数组来存放来自客户端的信息最大连接数为20

	serveraddr.sin_family = AF_INET;             //设置服务器地址家族
	serveraddr.sin_port = htons(4600);           //设置服务器端口号
	serveraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	m_SockServer = socket(AF_INET, SOCK_STREAM, 0);    //创建一个临时变量并赋值给m_SockServer
	int i = bind(m_SockServer, (sockaddr*)&serveraddr, sizeof(serveraddr));    //把名字和套接字绑定
	std::cout << "bind:" << i << std::endl;

	int iMaxConnect = 20;           //最大连接数
	int iLisRet;
	char buf[] = "THIS IS SERVER\0";
	char WarnBuf[] = "It,is voer Max connect\0";
	int len = sizeof(serveraddr);            //serveraddr所占的字节大小

	while (true){
		iLisRet = listen(m_SockServer, 0);    //进行监听
		int temp = 0;
		int Len = sizeof(serveraddrfrom);
		m_Server[iConnect] = accept(m_SockServer, (sockaddr*)&serveraddrfrom, &len);

		if (m_Server[iConnect] != INVALID_SOCKET)  //INVALID_SOCKET表示无效 
		{
			if (getsockname(m_Server[iConnect], (struct sockaddr*)&serveraddrfrom, &Len) != -1)
			{
				printf("listen address = %s:%d\n", inet_ntoa(serveraddrfrom.sin_addr), ntohs(serveraddrfrom.sin_port));
				sprintf(Ip[iConnect], "%s", inet_ntoa(serveraddrfrom.sin_addr));
			}
			else{
				printf("getsockname error\n");
				exit(0);
			}
			int ires = send(m_Server[iConnect], buf, sizeof(buf), 0);   //发送字符过去
			std::cout << "accept" << ires << std::endl << std::endl;    //显示已经连接次数                         
			iConnect++;
			if (iConnect > iMaxConnect){
				//判断连接数是否大于最大连接数 
				int ires = send(m_Server[iConnect], WarnBuf, sizeof(WarnBuf), 0);
			}
			else{	
				std::thread th1(test_threadpro,(void*)m_Server[--iConnect]); //启动线程
				th1.detach();
			}
		}
	}

	WSACleanup();     //用于释放ws2_32.dll动态链接库初始化时分配的资源
}

client端代码如下:

// Summary: Socket通信;
// Author:  jryang
// email:   muyijames@126.com
// Date:    2020-09-10

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<cstdlib>
#include<time.h>
#include<winsock2.h>                  //引用头文件
#pragma comment(lib,"ws2_32.lib")	  //链接库文件
using namespace std;

int main(void)
{
	WSADATA wsd;                       //定义WSADATA对象
	WSAStartup(MAKEWORD(2, 2), &wsd);
	SOCKET m_SockClient;               
	sockaddr_in serverAddr;           //服务器信息    
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(4600);
	serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	m_SockClient = socket(AF_INET, SOCK_STREAM, 0);
	int i = connect(m_SockClient, (sockaddr*)&serverAddr, sizeof(serverAddr));
	cout << "Connection status  " << i << endl;

	char buffer[1024];
	char inBuf[1024];
	int num;
	num = recv(m_SockClient, buffer, 1024, 0);             //阻塞函数,等待接受内容
	if (num > 0){
		cout << "Receive form server---" << buffer << endl;
		while (true){
			num = 0;
			cin >> inBuf;
			if (!strcmp(inBuf, "exit"))                     //如果输入的是exit则断开连接
			{
				send(m_SockClient, inBuf, sizeof(inBuf), 0);
				return 0;
			}
			send(m_SockClient, inBuf, sizeof(inBuf), 0);
			num = recv(m_SockClient, buffer, 1024, 0);
			if (num >= 0)
				cout << "Receive form server: " << buffer << endl;  //输出接受到的内容
		}
	}
}

有错误之处请多多指教哈!!!

  • 7
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值