Windows/Linux TCP Socket网络编程简介及测试代码
典型的网络应用是由一对程序(即客户程序和服务器程序)组成的,它们位于两个不同的端系统中。当运行这两个程序时,创建了一个客户进程和一个服务器进程,同时它们通过从套接字(socket)读出和写入数据在彼此之间进行通信。开发者创建一个网络应用时,其主要任务就是编写客户程序和服务器程序的代码。
网络应用程序有两类。一类是由协议标准(如一个RFC或某种其它标准文档)中所定义的操作的实现,这样的应用程序有时称为”开放”的,因为定义其操作的这些规则为人们所共知。对于这样的实现,客户程序和服务器程序必须遵守由该RFC所规定的规则。另一类网络应用程序是专用的网络应用程序。在这种情况下,由客户和服务器程序应用的应用层协议没有公开发布在某RFC中或其它地方。某单独的开发者(或开发团队)产生了客户和服务器程序,并且该开发者用他的代码完全控制该代码的功能。但是因为这些代码并没有实现一个开放的协议,其它独立的开发者将不能开发出和该应用程序交互的代码。
RFC(Request for Comments):请求意见稿,是由互联网工程任务组(IETF)发布的一系列备忘录。文件收集了有关互联网相关信息,以及UNIX和互联网社群的软件文件,以编号排定。目前RFC文件是由互联网协会(ISOC)赞助发行。基本的互联网通信协议都有在RFC文件内详细说明。RFC已经成为IETF、Internet Architecture Board(IAB)还有其他一些主要的公共网络研究社区的正式出版物发布途径。RFC文件只有新增,不会有取消或中途停止发行的情形。但是对于同一主题而言,新的RFC文件可以声明取代旧的RFC文件。
TCP网络编程有两种模式:一种是服务器模式,另一种是客户端模式。服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理;客户端模式则根据目的服务器的地址和端口进行连接,向服务器发送请求并对服务器的响应进行数据处理。
TCP是面向连接的,并且为两个端系统之间的数据流动提供可靠的字节流通道。UDP是无连接的,从一个端系统向另一个端系统发送独立的数据分组,不对交付提供任何保证。当客户或服务器程序实现了一个由RFC定义的协议时,它应当使用与该协议关联的周知端口号;与之相反,当研发一个专用应用程序时,研发者必须注意避免使用这些周知端口号。
与UDP不同,TCP是一个面向连接的协议。这意味着在客户和服务器能够开始互相发送数据之前,它们先要握手和创建一个TCP连接。TCP连接的一端与客户套接字相联系,另一端与服务器套接字相联系。当创建该TCP连接时,我们将其与客户套接字地址(IP地址和端口号)和服务器套接字地址(IP地址和端口号)关联起来。使用创建的TCP连接,当一侧要向另一侧发送数据时,它只需经过其套接字将数据丢进TCP连接。这与UDP不同,UDP服务器在将分组丢进套接字之前必须为其附上一个目的地地址。
TCP中客户程序和服务器程序的交互:客户具有向服务器发起接触的任务。服务器为了能够对客户的初始接触做出反应,服务器必须已经准备好。这意味着两件事:第一,TCP服务器在客户试图发起接触前必须作为进程运行起来。第二,服务器程序必须具有一扇特殊的门,更精确地说是一个特殊的套接字,该门欢迎来自运行在任意主机上的客户进程的某种初始接触。有时我们将客户的初始接触称为”敲欢迎之门”。随着服务器进程的运行,客户进程能够向服务器发起一个TCP连接。这是由客户程序通过创建一个TCP套接字完成的。当该客户生成其TCP套接字时,它指定了服务器中的欢迎套接字的地址,即服务器主机的IP地址及其套接字的端口号。生成其套接字后,该客户发起了一个三次握手并创建与服务器的一个TCP连接。发生在运输层的三次握手,对于客户和服务器程序是完全透明的。
在三次握手期间,客户进程敲服务器进程的欢迎之门。当该服务器”听”到敲门声时,它将生成一扇新门(更精确地讲是一个新套接字),它专门用于特定的客户。它是专门对客户进行连接的新生成的套接字,称为连接套接字。从应用程序的观点来看,客户套接字和服务器连接套接字直接通过一根管道连接,如下图所示:客户进程可以向它的套接字发送任意字节,并且TCP保证服务器进程能够按发送的顺序接收(通过连接套接字)每个字节。TCP因此在客户和服务器进程之间提供了可靠服务。此外,客户进程不仅能向它的套接字发送字节,也能从中接收字节;类似地,服务器进程不仅从它的连接套接字接收字节,也能向其发送字节。
创建客户套接字时未指定其端口号;相反,我们让操作系统为我们做此事。connect方法执行完后,执行三次握手,并在客户和服务器之间创建起一条TCP连接。在服务器调用accept方法后,客户和服务器则完成了握手。客户和服务器之间创建了一个TCP连接。借助于创建的TCP连接,客户与服务器现在能够通过该连接相互发送字节。使用TCP,从一侧发送的所有字节不仅能确保到达另一侧,而且确保按序到达。
TCP和UDP协议是以IP协议为基础的传输,为了方便多种应用程序,区分不同应用程序的数据和状态,引入了端口的概念。端口号是一个16比特的数,其大小在0~65535之间,通常称这个值为端口号。0~1023范围的端口号称为周知端口号(well-known port number),是受限制的,这是指它们保留给诸如HTTP(它使用端口号80)和FTP(它使用端口号21)之类的周知应用层协议来使用。当我们开发一个新的应用程序时,必须为其分配一个端口号。如果应用程序开发者所编写的代码实现的是一个”周知协议”的服务器端,那么开发者就必须为其分配一个相应的周知端口号。通常,应用程序的客户端让运输层自动地(并且是透明地)分配端口号,而服务器端则分配一个特定的端口号。如果是服务程序,则需要对某个端口进行绑定,这样某个客户端可以访问本主机上的此端口来与应用程序进行通信。由于IP地址只能对主机进行区分,而加上端口号就可以区分此主机上的应用程序。实际上,IP地址和端口号的组合,可以确定在网络上的一个程序通路,端口号实际上是操作系统标识应用程序的一种方法。
端口号的值可由用户自定义或者由系统分配,采用动态系统分配和静态用户自定义相结合的办法。一些常用的服务程序使用固定的静态端口号,例如,Web服务器的端口号为80,电子邮件SMTP的端口号为25,文件传输FTP的端口号为20和21等。对于其他的应用程序,特别是用户自行开发的客户端应用程序,端口号采用动态分配方法,其端口号由操作系统自动分配。通常情况下,对端口的使用有如下约定,小于1024的端口未保留端口,由系统的标准服务程序使用;1024以上的端口号,用户应用程序可以使用。
套接字有三种类型:流式套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)及原始套接字。
(1).流式套接字:可以提供可靠的、面向连接的通讯流。如果你通过流式套接字发送了顺序的数据:”1”、”2”,那么数据到达远程时候的顺序也是”1”、”2”。Telnet是流式连接。还有WWW浏览器,它使用的HTTP协议也是通过流式套接字来获取网页的。流式套接字使用了TCP(The Transmission Control Protocol)协议。TCP保证了你的数据传输是正确的,并且是顺序的。
(2).数据报套接字:定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。它使用数据报协议UDP(User Datagram Protocol)。UDP不像流式套接字那样维护一个打开的连接,你只需要把数据打成一个包,把远程的IP贴上去,然后把这个包发送出去。这个过程是不需要建立连接的。UDP的应用例子有:tfpt, bootp等。
(3).原始套接字:主要用于一些协议的开发,可以进行比较底层的操作。
流式套接字工作过程:服务器首先启动,通过调用socket建立一个套接字,然后调用bind将该套接字和本地网络地址联系在一起,再调用listen使套接字做好侦听的准备,并规定它的请求队列的长度,之后就调用accept来接收连接。客户在建立套接字后就可调用connect和服务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用recv和send来接收和发送数据。最后,待数据传送结束后,双方调用close关闭套接字。
网络字节序是指多字节变量在网络传输时的表示方法,网络字节序采用大端字节序的表示方法。这样小端字节序的系统通过网络传输变量的时候需要进行字节序的转换,大端字节序的变量则不需要进行转换。字节序是由于不同的主处理器和操作系统,对大于一个字节的变量在内存中的存放顺序不同而产生的。
小端字节序(Little Endian, LE):在表示变量的内存地址的起始地址存放低字节,高字节顺序存放。LE主要用于我们现在的PC的CPU中,即Intel的x86系列兼容机。
大端字节序(Bit Endian, BE):在表示变量的内存地址的起始地址存放高字节,低字节顺序存放。
注:以上内容主要摘自于:《计算机网络自顶向下方法(原书第7版)》、《Linux网络编程(第2版)》
以下是测试代码段,可同时在Windows和Linux下执行,并对代码中用到的函数进行了说明:
const char* server_ip_ = "10.4.96.33"; // 服务器ip
const int server_port_ = 8888; // 服务器端口号,需确保此端口未被占用// linux: $ netstat -nap | grep 6666; kill -9 PID// windows: tasklist | findstr OpenSSL_Test.exe; taskkill /T /F /PID PID
const int server_listen_queue_length_ = 100; // 服务器listen队列支持的最大长度
以上代码段是设置的三个全局常量,server_ip_是指定测试的服务器端ip,server_port为指定的服务器端端口号,server_listen_queue_length_为指定服务器端listen队列支持的最大长度。
#ifdef _MSC_VER
// 每一个WinSock应用程序必须在开始操作前初始化WinSock的动态链接库(DLL),并在操作完成后通知DLL进行清除操作
class WinSockInit {
public:WinSockInit(){WSADATA wsaData;// WinSock应用程序在开始时必须要调用WSAStartup函数,结束时调用WSACleanup函数// WSAStartup函数必须是WinSock应用程序调用的第一个WinSock函数,否则,其它的WinSock API函数都将会失败并返回错误值int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);if (ret != NO_ERROR)fprintf(stderr, "fail to init winsock: %d\n", ret);}~WinSockInit(){WSACleanup();}
};static WinSockInit win_sock_init_;#define close(fd) closesocket(fd)
#define socklen_t int
#else
#define SOCKET int
#endif
以上代码段主要是实现了WinSockInit类,此类仅在Windows下使用,用于WinSock的初始化。
int get_error_code()
{
#ifdef _MSC_VERauto err_code = WSAGetLastError();
#elseauto err_code = errno;
#endifreturn err_code;
}
get_error_code函数是为了获取调用相关函数时返回的错误码,在linux使用errno,windows上不支持errno,需要使用WSAGetLastError函数。
// 服务器端处理来自客户端的数据
void calc_string_length(SOCKET fd)
{// 从客户端接收数据const int length_recv_buf = 2048;char buf_recv[length_recv_buf];std::vector<char> recved_data;//std::this_thread::sleep_for(std::chrono::seconds(10)); // 为了验证客户端write或send会超时while (1) {auto num = recv(fd, buf_recv, length_recv_buf, 0);if (num <= 0) {auto err_code = get_error_code();if (num < 0 && err_code == EINTR) {continue;}std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to recv: %d, error code: %d, message: %s\n", num, err_code, ec.message().c_str());close(fd);return;}bool flag = false;std::for_each(buf_recv, buf_recv + num, [&flag, &recved_data](const char& c) {if (c == '\0') flag = true; // 以空字符作为接收结束的标志else recved_data.emplace_back(c);});if (flag == true) break;}fprintf(stdout, "recved data: ", recved_data.data());std::for_each(recved_data.data(), recved_data.data() + recved_data.size(), [](const char& c){fprintf(stdout, "%c", c);});fprintf(stdout, "\n");// 向客户端发送数据auto str = std::to_string(recved_data.size());std::vector<char> vec(str.size() + 1);memcpy(vec.data(), str.data(), str.size());vec[str.size()] = '\0';const char* ptr = vec.data();auto left_send = str.size() + 1; // 以空字符作为发送结束的标志//std::this_thread::sleep_for(std::chrono::seconds(10)); // 为了验证客户端read或recv会超时while (left_send > 0) {auto sended_length = send(fd, ptr, left_send, 0); // writeif (sended_length <= 0) {int err_code = get_error_code();if (sended_length < 0 && err_code == EINTR) {continue;}std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to send: %d, error code: %d, message: %s\n", sended_length, err_code, ec.message().c_str());close(fd);return;}left_send -= sended_length;ptr += sended_length;}close(fd);
}
以上代码段是服务器端调用的函数,服务器端程序会为每个连接上的客户程序创建一个线程,调用此函数来进行服务器端和客户端的数据的接收和发送处理。服务器端接收来自客户端的数据,并计算其长度,然后将其长度发送给客户端。
// 设置套接字为非阻塞的
int set_client_socket_nonblock(SOCKET fd)
{
#ifdef _MSC_VERu_long n = 1;// ioctlsocket: 通过将第2个参数设置为FIONBIO变更套接字fd的操作模式// 当此函数的第3个参数为true时,变更为非阻塞模式;为false时,变更为阻塞模式auto ret = ioctlsocket(fd, FIONBIO, &n);if (ret != 0) {fprintf(stderr, "fail to ioctlsocket: %d\n", ret);return -1;}
#else// fcntl: 向打开的套接字fd发送命令,更改其属性; F_GETFL/F_SETFL: 获得/设置套接字fd状态值; O_NONBLOCK: 设置套接字为非阻塞模式auto ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);if (ret < 0) {fprintf(stderr, "fail to fcntl: %d\n", ret);}
#endifreturn 0;
}// 设置套接字为阻塞的
int set_client_socket_block(SOCKET fd)
{
#ifdef _MSC_VERu_long n = 0;auto ret = ioctlsocket(fd, FIONBIO, &n);if (ret != 0) {fprintf(stderr, "fail to ioctlsocket: %d\n", ret);return -1;}
#elseauto ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);if (ret < 0) {fprintf(stderr, "fail to fcntl: %d\n", ret);}
#endifreturn 0;
}// 设置连接超时
int set_client_connect_time_out(SOCKET fd, const sockaddr* server_addr, socklen_t length, int seconds)
{
#ifdef _MSC_VERif (seconds <= 0) {
#elseif (fd >= FD_SETSIZE || seconds <= 0) {
#endifreturn connect(fd, server_addr, length);}set_client_socket_nonblock(fd);auto ret = connect(fd, server_addr, length);if (ret == 0) {set_client_socket_block(fd);fprintf(stdout, "non block connect return 0\n");return 0;}
#ifdef _MSC_VERelse if (ret == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) {
#elseelse if (ret < 0 && errno != EINPROGRESS) {
#endiffprintf(stderr, "non block connect fail return: %d\n", ret);return -1;}// 设置超时fd_set fdset;FD_ZERO(&fdset);FD_SET(fd, &fdset);struct timeval tv;tv.tv_sec = seconds;tv.tv_usec = 0;// select: 非阻塞方式,返回值:0:表示超时; 1:表示连接成功; -1:表示有错误发生// 注:在windows下select函数不作为计时器,在windows下,select的第一个参数可以忽略,可以是任意值ret = select(fd + 1, nullptr, &fdset, nullptr, &tv);if (ret < 0) {fprintf(stderr, "fail to select: %d\n", ret);return -1;} else if (ret == 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "connect time out: error code: %d, message: %s\n", fd, err_code, ec.message().c_str());return -1;} else {int optval;socklen_t optlen = sizeof(optval);
#ifdef _MSC_VERret = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&optval, &optlen);
#else// getsockopt: 获得套接字选项设置情况,此函数的第3个参数SO_ERROR表示获取错误ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
#endifif (ret == -1 || optval != 0) {fprintf(stderr, "fail to getsockopt\n");return -1;}if (optval == 0) {set_client_socket_block(fd);fprintf(stdout, "connect did not time out\n");return 0;}}return 0;
}
以上代码段是用来设置客户端连接超时,通过调用select函数作为计时器,默认的connect、accept、recv、send函数都是属于阻塞方式,而select是非阻塞方式。select一般作为计时器仅用于非Windows平台,因为select的第一个参数套接字在windows上不起作用。
int set_client_send_time_out(SOCKET fd, int seconds)
{if (seconds <= 0) {fprintf(stderr, "seconds should be greater than 0: %d\n", seconds);return -1;}#ifdef _MSC_VERDWORD timeout = seconds * 1000; // millisecondsauto ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
#elsestruct timeval timeout;timeout.tv_sec = seconds;timeout.tv_usec = 0;// setsockopt: 设置套接字选项,为了操作套接字层的选项,此函数的第2个参数的值需指定为SOL_SOCKET,第3个参数SO_SNDTIMEO表示发送超时,第4个参数指定超时时间// 默认情况下send函数在发送数据的时候是不会超时的,当没有数据的时候会永远阻塞auto ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
#endifif (ret < 0) {fprintf(stderr, "fail to setsockopt: send\n");return -1;}return 0;
}// 设置接收数据recv超时
int set_client_recv_time_out(SOCKET fd, int seconds)
{if (seconds <= 0) {fprintf(stderr, "seconds should be greater than 0: %d\n", seconds);return -1;}#ifdef _MSC_VERDWORD timeout = seconds * 1000; // millisecondsauto ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
#elsestruct timeval timeout;timeout.tv_sec = seconds;timeout.tv_usec = 0;// setsockopt: 此函数的第3个参数SO_RCVTIMEO表示接收超时,第4个参数指定超时时间// 默认情况下recv函数在接收数据的时候是不会超时的,当没有数据的时候会永远阻塞auto ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
#endifif (ret < 0) {fprintf(stderr, "fail to setsockopt: recv\n");return -1;}return 0;
}
以上代码段是用来设置客户端接收和发送数据超时,主要通过setsockopt函数实现。
int test_socket_tcp_client()
{// 1.创建流式套接字auto fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to socket: %d, error code: %d, message: %s\n", fd, err_code, ec.message().c_str());return -1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port_);auto ret = inet_pton(AF_INET, server_ip_, &server_addr.sin_addr);if (ret != 1) {fprintf(stderr, "fail to inet_pton: %d\n", ret);return -1;}set_client_send_time_out(fd, 2); // 设置write或send超时时间set_client_recv_time_out(fd, 2); // 设置read或recv超时时间// 2.连接// connect函数的第二参数是一个指向数据结构sockaddr的指针,其中包括客户端需要连接的服务器的目的端口和IP地址,以及协议类型ret = set_client_connect_time_out(fd, (struct sockaddr*)&server_addr, sizeof(server_addr), 2); // 设置连接超时时间//ret = connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));if (ret != 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to connect: %d, error code: %d, message: %s\n", ret, err_code, ec.message().c_str());return -1;}// 3.接收和发送数据// 向服务器端发送数据const char* buf_send = "https://blog.csdn.net/fengbingchun";const char* ptr = buf_send;auto length = strlen(buf_send);auto left_send = length + 1; // 以空字符作为发送结束的标志// 以下注释掉的code仅用于测试write或send超时时间//std::unique_ptr<char> buf_send(new char[1024 * 1024]);//int length = 1024 * 1024;//long long count = 0;//for (;;) {//int left_send = length + 1;//const char* ptr = buf_send.get();//fprintf(stdout, "count: %lld\n", ++count);while (left_send > 0) {// send: 将缓冲区ptr中大小为left_send的数据,通过套接字文件描述符fd按照第4个参数flags指定的方式发送出去// send的返回值是成功发送的字节数.由于用户缓冲区ptr中的数据在通过send函数进行发送的时候,并不一定能够// 全部发送出去,所以要检查send函数的返回值,按照与计划发送的字节长度left_send是否相等来判断如何进行下一步操作// 当send的返回值小于left_send的时候,表明缓冲区中仍然有部分数据没有成功发送,这是需要重新发送剩余部分的数据// send发生错误的时候返回值为-1// 注意:send的成功返回并不一定意味着数据已经送到了网络中,只说明协议栈有足够的空间缓存数据,协议栈可能会为了遵循协议的约定推迟传输auto sended_length = send(fd, ptr, left_send, 0); // writeif (sended_length <= 0) {auto err_code = get_error_code();if (sended_length < 0 && err_code == EINTR) {continue;}std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to send: %d, err code: %d, message: %s\n", sended_length, err_code, ec.message().c_str());return -1;}left_send -= sended_length;ptr += sended_length;}//}// 从服务器端接收数据const int length_recv_buf = 2048;char buf_recv[length_recv_buf];std::vector<char> recved_data;while (1) {// recv: 用于接收数据,从套接字fd中接收数据放到缓冲区buf_recv中,第4个参数用于设置接收数据的方式// recv的返回值是成功接收到的字节数,当返回值为-1时错误发生auto num = recv(fd, buf_recv, length_recv_buf, 0); // readif (num <= 0) {auto err_code = get_error_code();if (num < 0 && err_code == EINTR) {continue;}std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to recv: %d, err code: %d, message: %s\n", num, err_code, ec.message().c_str());return -1;}bool flag = false;std::for_each(buf_recv, buf_recv + num, [&flag, &recved_data](const char& c) {if (c == '\0') flag = true; // 以空字符作为接收结束的标志else recved_data.emplace_back(c);});if (flag == true) break;}// 4.关闭套接字close(fd);// 验证接收的数据是否是预期的fprintf(stdout, "send data: %s\n", buf_send, recved_data.data());fprintf(stdout, "recved data: ");std::for_each(recved_data.data(), recved_data.data() + recved_data.size(), [](const char& c){fprintf(stdout, "%c", c);});fprintf(stdout, "\n");std::string str(recved_data.data());auto length2 = std::stoi(str);if (length != length2) {fprintf(stderr, "received data is wrong: %d, %d\n", length, length2);return -1;}return 0;
}
以上代码段是客户端程序实现,同时支持在Windows和Linux上运行。
int test_socket_tcp_server()
{// 1.创建流式套接字// socket:参数依次为协议族、协议类型、协议编号. AF_INET: 以太网;// SOCK_STREAM:流式套接字,TCP连接,提供序列化的、可靠的、双向连接的字节流auto fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to socket: %d, error code: %d, message: %s\n", fd, err_code, ec.message().c_str());return -1;}// 2.绑定地址端口// sockaddr_in: 以太网套接字地址数据结构,与结构sockaddr大小完全一致struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;// htons: 网络字节序转换函数,还包括htonl, ntohs, ntohl等,// 其中s是short数据类型的意思,l是long数据类型的意思,而h是host,即主机的意思,n是network,即网络的意思,// htons: 表示对于short类型的变量,从主机字节序转换为网络字节序server_addr.sin_port = htons(server_port_);// inet_xxx: 字符串IP地址和二进制IP地址转换函数// inet_pton: 将字符串类型的IP地址转换为二进制类型,第1个参数表示网络类型的协议族auto ret = inet_pton(AF_INET, server_ip_, &server_addr.sin_addr);if (ret != 1) {fprintf(stderr, "fail to inet_pton: %d\n", ret);return -1;}// sockaddr: 通用的套接字地址数据结构,它可以在不同协议族之间进行转换,包含了地址、端口和IP地址的信息ret = bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));if (ret != 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to bind: %d, error code: %d, message: %s\n", ret, err_code, ec.message().c_str());return -1;}//std::this_thread::sleep_for(std::chrono::seconds(30)); // 为了验证客户端连接会超时// 3.监听端口// listen: 用来初始化服务器可连接队列,服务器处理客户端连接请求的时候是顺序处理的,同一时间仅能处理一个客户端连接.// 当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度有listen函数来定义// listen的第二个参数表示在accept函数处理之前在等待队列中的客户端的长度,如果超过这个长度,客户端会返回一个错误ret = listen(fd, server_listen_queue_length_);if (ret != 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to listen: %d, error code: %d, message: %s\n", ret, err_code, ec.message().c_str());return -1;}while (1) {struct sockaddr_in client_addr;socklen_t length = sizeof(client_addr);// 4.接收客户端的连接,在这个过程中客户端与服务器进行三次握手,建立TCP连接// accept成功执行后,会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以通过这个新描述符来获得// 当服务器成功处理客户端的请求连接后,会有两个文件描述符,老的文件描述符表示正在监听的socket,新产生的文件描述符表示客户端的连接auto fd2 = accept(fd, (struct sockaddr*)&client_addr, &length);if (fd2 < 0) {auto err_code = get_error_code();std::error_code ec(err_code, std::system_category());fprintf(stderr, "fail to accept: %d, error code: %d, message: %s\n", fd2, err_code, ec.message().c_str());continue;}struct in_addr addr;addr.s_addr = client_addr.sin_addr.s_addr;// inet_ntoa: 将二进制类型的IP地址转换为字符串类型fprintf(stdout, "client ip: %s\n", inet_ntoa(addr));// 5.接收和发送数据// 连接上的每一个客户都有单独的线程来处理std::thread(calc_string_length, fd2).detach();}// 关闭套接字close(fd);return 0;
}
以上代码段是服务器端程序实现,同时支持Windows和Linux上运行。
以下是Linux作为服务器端,Windows作为客户端时的执行结果:服务器端程序先启动
以上测试的完整代码见:GitHub OpenSSL_Test/funset_socket.cpp
GitHub:https://github.com/fengbingchun/OpenSSL_Test
相关文章:

《评人工智能如何走向新阶段》后记(再续3)
由AI科技大本营下载自视觉中国35.阿里巴巴旗下芯片公司平头哥在乌镇互联网大会上宣布开源低功耗微控制芯片(MCU)设计平台,这一平台面向 AIoT 时代的定制化芯片设计需求,目标群体包括芯片设计公司、IP 供应商、高校及科研院所等&am…

ffmpeg 基本用法大全
FFmpegFFmpeg 基本用法本课要解决的问题1.FFmpeg的转码流程是什么?2.常见的视频格式包含哪些内容吗?3.如何把这些内容从视频文件中抽取出来?4.如何从一种格式转换为另一种格式?5.如何放大和缩小视频?6.如何旋转&#x…

快过年了,为过完年跳槽的人准备一份面试题
设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。 1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。 2). MV…

Ubuntu上Vim安装NERDTree插件操作步骤
NERDTree是Vim的文件系统浏览器,使用此插件,用户可以直观地浏览复杂的目录层次结构,快速打开文件以进行读取或编辑,以及执行基本的文件系统操作。NERDTree源码在https://github.com/preservim/nerdtree。 这里通过Vundle安装NERD…

《评人工智能如何走向新阶段》后记(再续4)
由AI科技大本营下载自视觉中国41. 在人工智能感知阶段,依靠数据驱动的深度学习算法。目前5种最流行的深度学习架构: ① 递归神经网络(RNN)② 长短期记忆 (LSTM)/门控递归单元(GRU)③卷积神经网络…

电视游戏会是未来客厅娱乐的主角吗?
在时下流行的多屏生态概念中,电视虽为最大屏幕,但与智能手机、平板等小屏相比,属于相对较弱的一环。无移动性、自身交互性不足,在一定程度上影响着它在移动时代的发展。而作为最能体现其“吸睛能力”的——大屏娱乐功能࿰…

王爽著的《汇编语言》第3版笔记
王爽著的《汇编语言》(第3版)于2013年出版,虽然是2013年出版的,但书中部分内容感觉已过时: (1). 基于intel 8086 CPU介绍,intel 8086是英特尔公司上个世纪生产的芯片,是16位的,早已停产; (2).…

iOS 倒计时方法
//启动计时器 double delayInSeconds 10.0; dispatch_time_t popTime dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ //执行事件 NSLog("计时器结束了"); UIAlertController …

《评人工智能如何走向新阶段》后记(再续5)
由AI科技大本营下载自视觉中国51.今年发表的由俄罗斯“脑机接口”公司(Neurobotics)和莫斯科物理技术学院(MIPT)研究的一种全新“脑机接口”算法。利用“脑机接口”将人脑(EEG)神经元与脑外深度学习网络连接…

不说12306你会Die啊?当然不会,但会憋死
别嫌这标题话粗啊,只是突然想起了某小品中的一句台词儿而已。又是一年春运时,几十亿人口开始了兴奋着、痛苦着的大迁徙。12306开通有几年了吧,我今年才第一次用。因为父母要回老家,不想排队那么辛苦,所以才尝试一把网络…

汇编程序设计与计算机体系结构软件工程师教程笔记:处理器、寄存器简介
《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,…
iOS 发布APP关于IDFA的相关内容
您的 App 正在使用广告标识符 (IDFA)。您必须先提供关于 IDFA 的使用信息或将其从 App 中移除,然后再上传您的二进制文件。 如果出现下边这两张图,你就会感到蛋蛋的忧伤 还有这个 怎么解决? 1,查看你所集成的SDK,看看…

《评人工智能如何走向新阶段》后记(再续6)
由AI科技大本营下载自视觉中国61. 在2019深度学习开发者峰会上,百度发布基于飞桨的图学习框架(PaddleGraphLearning,PGL)。近年来深度神经网络推动了人工智能的发展,但在实际场景中有大量数据是在非欧式空间的…

Android APP测试的日志文件抓取
1 log文件分类简介 实时打印的主要有:logcat main,logcat radio,logcat events,tcpdump,还有高通平台的还会有QXDM日志 状态信息的有:adb shell cat /proc/kmsg ,adb shell dmesg,…

汇编程序设计与计算机体系结构软件工程师教程笔记:指令
《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,…

IOS视频编辑功能详解上篇-添加水印
前言 用代码在简单视频编辑中,主要就是加美颜、水印(贴图)、视频截取、视频拼接、音视频的处理,在美颜中,使用GPUImage即可实现多种滤镜、磨皮美颜的功能,并且可以脸部识别实时美颜等功能,这个…

《评人工智能如何走向新阶段》后记(再续7)
由AI科技大本营下载自视觉中国66. 谷歌近来研发用于基因科学的人工智能AlphaFold,根据基因序列预测生命基本分子一蛋白质的三维结构(AlphaFold与下国际围棋的AlphaGo似孪生兄弟),这是用来预测蛋白质折叠结构的能力或设计新的蛋白质…

Linux多线程实践(6) --Posix读写锁解决读者写者问题
Posix读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_r…

iOS-直播开发(开发从底层做起)
代码链接: Github: https://github.com/jessonliu/JFLivePlaye 技术部分------ ⬇️ 脑涂: ![ 直播思维导图.png ] 视频直播的大概流程就上脑涂上所画的, 还有一些没列出来, 比如, 聊天, 送礼, 踢出, 禁言, 等等一系列功能, 但本文只是针对视频直播的简单实现! 下边来说一下以…

汇编程序设计与计算机体系结构软件工程师教程笔记:函数、字符串、浮点运算
《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,…
《庆余年》值得一看吗?Python告诉你谁在关注 | CSDN原力计划
扫码参与CSDN“原力计划”作者 | A字头来源 | 数据札记倌庆余年电视剧终于在前两天上了,这两天赶紧爬取数据看一下它的表现。庆余年《庆余年》是作家猫腻的小说。这部从2007年就开更的作品拥有固定的书迷群体,也在文学IP价值榜上有名。期待已久的影视版的…

《C语言及程序设计》实践项目——画分支结构流程图
返回:贺老师课程教学链接 【单分支结构流程图-大值】问题:画流程图,输入两个整数a和b,输出其中的大值。提示:当a<b时,交换a和b,最后输出的a一定是其中的大值。流程图中可以直接给出交换a和b…

汇编程序设计与计算机体系结构软件工程师教程笔记:内联汇编与宏
《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,…
无需标注数据,利用辅助性旋转损失的自监督GANs,效果堪比现有最好方法
作者 | Ting Chen译者 | 王红成出品 | AI科技大本营(ID:rgznai100)本文作者提出了一种自检督方式的生成对抗网络,通过辅助性的旋转损失来达到目的。因为通常主流方法来生成自然图像都是通过条件GAN来完成,但是这就需要很多的标签数…

iOS环信聊天界面中点击头像和消息的几种状态
/*环信自带头像点击事件*/ - (void)messageViewController:(EaseMessageViewController *)viewControllerdidSelectAvatarMessageModel:(id<IMessageModel>)messageModel {内容可以根据需要自己添加 }/*!methodbrief 点击了简历消息 (lyq添加)discussion 点击了简历消息,…

ASP.NET将Session保存到数据库中
因为ASP.NET中Session的存取机制与ASP相同,都是保存在进行中, 一旦进程崩溃,所有Session信息将会丢失,所以我采取了将Session信息保存到SQL Server中,尽管还有其它的几个方式(本文不作介绍)&…

iOS App上架流程
一、前言:作为一名iOSer,把开发出来的App上传到App Store是必要的。下面就来详细讲解一下具体流程步骤。 二、准备: 一个已付费的开发者账号(账号类型分为个人(Individual)、公司(Company&#…
不止Markov决策过程,全景式分析强化学习研究内容
作者 | 肖智清编辑 | 刘静来源 | CSDN(ID:CSDNnews)强化学习作为通用人工智能的希望,吸引了很多人工智能爱好者学习和研究。Markov决策过程是最知名的强化学习模型,强化学习教程也常以Markov决策过程作为起点。但是&am…

Windows下创建进程简介
正在执行的应用程序称为进程,进程不仅仅是指令和数据,它还有状态。状态是保存在处理器寄存器中的一组值,如当前执行指令的地址、保存在内存中的值,以及唯一定义进程在任一时刻任务的所有其他值。进程与应用程序的一个重要的区别在…

jQuery中鲜为人知的的几个方法
转来学习一下 jQuery中鲜为人知的的几个方法 jQuery近些年来仍旧是web开发中最受欢迎的类库,虽然大家褒贬不一,但是仍旧不失为一款最流行的Javascript,在今天这篇文章中,我们将介绍几个jQuery的相关方法,无论你是入门级…