网络编程 socket

目录

  • 网络编程 套接字(socket)
    • 1. 认识端口号
    • 2. TCP协议
    • 3. UDP协议
    • 4. 网络字节序列
    • 5. 常见的套接字
    • 6. socket编程接口
      • 6.1 socket常见API
        • socket函数
        • recvfrom函数
        • sendto函数
        • read函数 从tcp socket中读取接收数据
      • 6.2 sockaddr结构
      • 6.3 地址转换函数
      • 6.4 udp socket实例
        • 本地回环地址 127.0.0.1
      • 6.5 tcp实例

网络编程 套接字(socket)

真正的网络通信过程,本质其实是进程间通信。 将数据在主机间转发仅仅是手段,机器收到之后,需要将数据交付给指定的进程。

因此,仅仅有ip地址是无法完成通信的,有了ip地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出,这个数据要给哪个程序进行解析。

IP地址+端口号称为套接字(SRC_IP+SRC_PORT -> 套接字),因此称为套接字编程。

1. 认识端口号

端口号,标识特定主机上的网络进程的唯一性。

端口号(port)是传输层协议的内容。

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
  • IP地址+端口号能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用,一个进程可以绑定多个端口号。

不是所有的进程都需要端口号。

2. TCP协议

TCP(Transmission Control Protocol 传输控制协议)

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

3. UDP协议

UDP(User Datagram Protocol 用户数据报协议)

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

4. 网络字节序列

image-20230630211618012

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>unit32_t htonl(unit32_t hostlong);  //将32位的长整数从主机字节序转换为网络字节序
unit16_t htons(unit16_t hostshort); //将16位的短整数从主机字节序转换为网络字节序
unit32_t ntohl(unit32_t netlong);   //将32位的长整数从网络字节序转换为主机字节序
unit16_t ntohs(unit16_t netshort);  //将16位的短整数从网络字节序转换为主机字节序
  • h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

5. 常见的套接字

  1. 域间socket (本主机内进程间通信,也被称为一种基于套接字接口的管道通信策略)
  2. 原始socket(不常见,通常用来编写工具,允许绕传输层直接使用网络层,或绕过其他直接使用底层)
  3. 网络socket
    理论上,上面三种套接字,是三种应用场景,对应的应该是三套接口。
    但实际上为了设计简单化,将所有的接口进行统一。

6. socket编程接口

网络通信的套接字的标准是基于process的。

6.1 socket常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);  //backlog:全链接队列长度
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);//成功返回一个整数,表示接受的socket
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 网络发送接口(tcp)
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

socket函数

**创建套接字的一个系统调用接口。**属于计算机网络提供的一个系统调用接口,是对传输层做了相关的一层文件系统级别的封装的接口。

成功返回创建的套接字文件描述符,失败返回-1。

image-20230701212140368

  • domain:指定协议族,常用的有AF_INET(IPv4)和AF_INET6(IPv6)。(网络通信or本地通信?)
  • type:套接字类型,常用的有SOCK_STREAM(流套接字,提供可靠的、面向连接的通信)和SOCK_DGRAM(数据报套接字,提供不可靠的、无连接的通信)。
  • protocol:指定协议,一般为0,表示根据domain和type自动选择合适的协议。

recvfrom函数

recvfrom函数是用于接收数据的系统调用函数,它从udp套接字接收数据,并将其存储到指定的缓冲区中。recvfrom函数的返回值是一个整数,表示接收到的数据的字节数。

image-20230701212057033

  • 如果返回值大于0,表示成功接收到数据,并返回接收到的字节数。
  • 如果返回值为0,表示对方已经关闭了连接。
  • 如果返回值为-1,表示接收数据出现错误,可以通过检查errno来获取具体的错误原因。

sendto函数

发送数据到指定的目标地址

image-20230702101223126

注意这里的addrlen不是指针,所以传的时候不用传地址。

read函数 从tcp socket中读取接收数据

image-20230708114748562

同理,在tcp socket中,使用write进行写。

6.2 sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6等,然而,各种网络协议的地址格式并不相同。

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址。

IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in;这样的好
处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为
参数。

基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息:地址类型,端口号,IP地址。

image-20230715154614970

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

image-20230715154653771

6.3 地址转换函数

  • 字符串转in_addr
    image-20230715155050436

  • in_addr 转字符串

    image-20230715155131968

其中inet_ptoninet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr

6.4 udp socket实例

  1. 下面是一个简单的echo server例子。echo server:client给server发送消息,server原封不动返回

udp_server.hpp

#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"#define SIZE 1024class UdpServer{
public:UdpServer(uint16_t port, std::string ip="0.0.0.0") :_port(port), _ip(ip), _sock(-1){}bool initServer(){//从这里开始,就是新的系统调用,来完成网络功能//1.创建套接字_sock = socket(AF_INET, SOCK_DGRAM, 0);if(_sock < 0){logMessage(FATAL, "%d : %s", errno, strerror(errno));exit(2);}//2. bind:将用户设置的ip和port在内核中和我们当前的进程强关联// "192.168.1.3" -> 点分十进制字符串风格的IP地址//每一个区域取值范围是[0-255]:1字节 2^8 -> 4个区域//点分十进制字符串风格的IP地址 <-> 4字节struct sockaddr_in local;bzero(&local, sizeof(local)); //当前字段清零local.sin_family = AF_INET;//ipv4 协议家族/域//服务器的IP和端口未来也是要发送给对方主机的 -> 先要将数据发送到网络local.sin_port = htons(_port);  //转参//1. 同上,点分十进制先要将字符串风格的IP地址-> 4字节IP//2. 4字节主机序列 -> 网络序列//有一套接口,可以一次帮我们做完这两件事情local.sin_addr.s_addr = inet_addr(_ip.c_str());//更改为从任意IP中获取信息//local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0){logMessage(FATAL, "%d : %s", errno, strerror(errno));exit(2);            }logMessage(NORMAL, "init udp server done ... %s", strerror(errno));//donereturn true;}void Start(){//作为一款网络服务器,永远不退出的!所以是一个死循环//服务器启动 -> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了/宕机//echo server:client给我们发送消息,我们原封不动返回char buffer[SIZE];for( ; ;){//注意://peer,纯输出型参数struct sockaddr_in peer;bzero(&peer, sizeof(peer));//输入:peer缓冲区大小//输出:实际读到的peersocklen_t len = sizeof(peer);  //输入输出型参数//start 读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(s > 0){buffer[s] = 0; //目前数据当做字符串uint16_t cli_port = ntohs(peer.sin_port); //peer是从网络中来的,需转换std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP->本主机的点分十进制字符串风格的IP地址printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);}//分析和处理数据, TODO//end. 写回数据sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);}}~UdpServer(){if(_sock >= 0) close(_sock);}
private://一个服务器,一般必须需要ip地址和portuint16_t _port;std::string _ip;int _sock;
};
#endif

udp_server.cc

#include "udp_server.hpp"
#include <memory>static void usage(std::string proc){std::cout << "\nUsage: " << proc << "ip port\n" << std::endl;
}
int main(int argc, char *argv[]){if(argc != 3){usage(argv[0]);exit(1);}std::string ip = argv[1];uint16_t port = atoi(argv[2]);std::unique_ptr<UdpServer> svr(new UdpServer(port, ip));svr->initServer();svr->Start();return 0;
}

image-20230702105032422

完善client端代码。

client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>static void usage(std::string proc){std::cout << "\nUsage: " << proc << "serverIp serverPort\n" << std::endl;
}
int main(int argc, char *argv[]){if(argc != 3){usage(argv[0]);exit(1);}//创建套接字int sock = socket(AF_INET, SOCK_DGRAM, 0);if(sock < 0){std::cerr << "socket error" << std::endl;exit(2);}//client一般不需要显示的bind指定port,而是让OS自动随机选择(什么时候选择)std::string message;struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(atoi(argv[2]));server.sin_addr.s_addr = inet_addr(argv[1]);char buffer[1024];while(true){std::cout << "请输入你的信息# ";std::getline(std::cin, message);//当client首次发送消息给服务器的时候,OS会自动给client bind它的IP和PORTsendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl;}}close(sock);return 0;
}

测试结果:

image-20230702142853286

以上就实现了本地间的通信。

通过netstat查看网络连接等信息。

image-20230702145422244

本地回环地址 127.0.0.1

127.0.0.1 是一个特殊的 IP 地址,被称为本地回环地址(loopback address)。它通常用于本机测试和网络通信的目的。

当你在计算机上使用 127.0.0.1 作为目标地址时,数据将被发送到本机的网络堆栈,而不会通过网络接口发送出去。这使得你可以在本机上模拟网络通信,而无需实际连接到外部网络。

本地回环地址 127.0.0.1 在 IPv4 中被保留,而在 IPv6 中,本地回环地址是 ::1

在网络编程中,可以使用 127.0.0.1 来测试和调试网络应用程序,或者在本地搭建服务器和客户端进行通信。

  1. 通过popen模拟shell,client发出命令,server处理并返回执行结果
void Start(){//作为一款网络服务器,永远不退出的!所以是一个死循环//服务器启动 -> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了/宕机//echo server:client给我们发送消息,我们原封不动返回char buffer[SIZE];for( ; ;){//注意://peer,纯输出型参数struct sockaddr_in peer;bzero(&peer, sizeof(peer));//输入:peer缓冲区大小//输出:实际读到的peersocklen_t len = sizeof(peer);  //输入输出型参数char result[256];std::string cmd_echo;//start 读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(s > 0){buffer[s] = 0; //目前数据当做字符串if(strcasestr(buffer, "rm") != nullptr || strcasestr(buffer, "rmdir") != nullptr){std::string err_message = "坏人...."; std::cout << err_message << buffer << std::endl;sendto(_sock, err_message.c_str(), err_message.size(), 0, (struct sockaddr*)&peer, len);continue;}FILE *fp = popen(buffer, "r");if(nullptr == fp){logMessage(ERROR, "popen: %d:%s", errno, strerror(errno));continue;}while (fgets(result, sizeof(result), fp) != nullptr){cmd_echo += result;}fclose(fp);}//分析和处理数据, TODO//end. 写回数据// sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);sendto(_sock, cmd_echo.c_str(), cmd_echo.size(), 0, (struct sockaddr*)&peer, len);}}
}
  1. 使用多线程实现客户端之间通讯

client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "thread.hpp"uint16_t serverport = 0;
std::string serverip;// int sock = -1;
static void usage(std::string proc){std::cout << "\nUsage: " << proc << "serverIp serverPort\n" << std::endl;
}static void *udpSend(void *args){int sock = *(int*)((ThreadData*)args)->args_;    //线程套接字std::string name = ((ThreadData*)args)->name_;   //线程名std::string message;struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());//不断进行发送while (true){std::cerr << "请输入你的信息# "; //标准错误 2打印std::getline(std::cin, message);if(message == "quit")break;//当client首次发送消息给服务器的时候,OS会自动给client bind它的IP和PORTsendto(sock, message.c_str() , message.size(), 0, (struct sockaddr*)&server, sizeof(server));}return nullptr;
}
static void *udpRecv(void *args){int sock = *(int*)((ThreadData*)args)->args_;    //线程套接字std::string name = ((ThreadData*)args)->name_;   //线程名char buffer[1024];while(true){//初始化为0memset(buffer, 0, sizeof(buffer));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);if(s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}}int main(int argc, char *argv[]){if(argc != 3){usage(argv[0]);exit(1);}//创建套接字int sock = socket(AF_INET, SOCK_DGRAM, 0);if(sock < 0){std::cerr << "socket error" << std::endl;exit(2);}//初始化port和ipserverport = atoi(argv[2]);serverip = argv[1];/*线程编号,回调方式,传入套接字*/std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void*)&sock));std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void*)&sock));// sender->name();sender->start();recver->start();sender->join();recver->join();close(sock);return 0;
}

udp_server.hpp

void Start(){//作为一款网络服务器,永远不退出的!所以是一个死循环//服务器启动 -> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了/宕机//echo server:client给我们发送消息,我们原封不动返回char buffer[SIZE];for( ; ;){//注意://peer,纯输出型参数struct sockaddr_in peer;bzero(&peer, sizeof(peer));//输入:peer缓冲区大小//输出:实际读到的peersocklen_t len = sizeof(peer);  //输入输出型参数char result[256];char key[64];std::string cmd_echo;//start 读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(s > 0){buffer[s] = 0; //目前数据当做字符串uint16_t cli_port = ntohs(peer.sin_port); //peer是从网络中来的,需转换std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP->本主机的点分十进制字符串风格的IP地址//简单的用户托管snprintf(key, sizeof key, "%s-%d", cli_ip.c_str(), cli_port); // 127.0.0.1-8080logMessage(NORMAL, "key: %s", key);auto it = _users.find(key);if(it == _users.end()){//not exitslogMessage(NORMAL, "add new user : %s", key);_users.insert({key, peer});}}for(auto &iter : _users){/*key,即前面的用ip和port来标识一个用户的身份*/std::string sendMessage = key;sendMessage += "#";sendMessage += buffer;  //127.0.0.1-1234# 你好logMessage(NORMAL, "push message to %s", iter.first.c_str());sendto(_sock, sendMessage.c_str(), sendMessage.size(),0, (struct sockaddr *)&(iter.second), sizeof(iter.second));}}
}

image-20230706224716087

注意:

  • 云服务器无法直接绑定公网IP非127.0.0.1非0.0.0.0这样的IP,即云服务器无法绑定公网IP。

  • 对于服务器来讲,不推荐绑定一个确定的IP,推荐使用绑定任意IP的方案。 即让服务器在工作过程中,可以从指定端口中的任意IP中获取数据。image-20230702150414085

    image-20230702151552623

  • 无论是多线程读还是写,用的sock都是一个,sock代表一个文件,可以同时进行收发的原因

    • UDP是全双工的,可以同时进行收发而不受干扰

6.5 tcp实例

可以通过对回调函数service的不同实现,来实现server端对client端的通信返回内容。下面是三个对回调函数不同实现的例子。

  • static void service( ); //简单的echoserver

  • static void change( ); //对于client发送到server的所有小写字母,转换为大写字母,再返回

  • static void dictOnline( ); //实现一个小词典

同样,当获取连接成功时,也列举了四种方式进行通信服务。

tcp_server.hpp

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <signal.h>
#include <memory>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "ThreadPool/Task.hpp"
#include "ThreadPool/log.hpp"
#include "ThreadPool/threadPool.hpp"
#include <netinet/in.h>// // 接收到客户端数据后进行的处理
static void service(int sock, const std::string &clientip,const uint16_t &clientport, const std::string &thread_name)
{// echo serverchar buffer[1024];while (true){// read && write 可以直接被使用ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0; // 将对应的发过来的数据当作字符串std::cout << thread_name << "|" << clientip << ":" << clientport << "#" << buffer << std::endl;}else if (s == 0) // 读取到返回值为0,代表对端关闭连接{logMessage(NORMAL, "%s : %d shutdown, me too!", clientip.c_str(), clientport);break;}else // 异常{logMessage(FATAL, "read socket error%d:%s", errno, strerror(errno));break;}// 读取成功,继续向socket写入write(sock, buffer, strlen(buffer));}close(sock);
}static void change(int sock, const std::string &clientip,const uint16_t &clientport, const std::string &thread_name)
{// 大小写转换char buffer[1024];// read && write 可以直接被使用ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0; // 将对应的发过来的数据当作字符串std::cout << thread_name << "|" << clientip << ":" << clientport << "#" << buffer << std::endl;std::string message;char *start = buffer;while (*start){char c;if (islower(*start))c = toupper(*start);elsec = *start;message.push_back(c);start++;}write(sock, message.c_str(), message.size());}else if (s == 0) // 读取到返回值为0,代表对端关闭连接{logMessage(NORMAL, "%s : %d shutdown, me too!", clientip.c_str(), clientport);}else // 异常{logMessage(FATAL, "read socket error%d:%s", errno, strerror(errno));}// 读取成功,继续向socket写入write(sock, buffer, strlen(buffer));close(sock);
}static void dictOnline(int sock, const std::string &clientip,const uint16_t &clientport, const std::string &thread_name)
{// echo serverchar buffer[1024];static std::unordered_map<std::string, std::string> dict = {{"apple", "苹果"},{"bite", "比特"},{"banana", "香蕉"},{"hard", "好难啊"}};// read && write 可以直接被使用ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0; // 将对应的发过来的数据当作字符串std::cout << thread_name << "|" << clientip << ":" << clientport << "#" << buffer << std::endl;std::string message;auto iter = dict.find(buffer);if(iter == dict.end()) message = "我不知道...";else message = iter->second;write(sock, message.c_str(), message.size());}else if (s == 0) // 读取到返回值为0,代表对端关闭连接{logMessage(NORMAL, "%s : %d shutdown, me too!", clientip.c_str(), clientport);}else // 异常{logMessage(FATAL, "read socket error%d:%s", errno, strerror(errno));}// 读取成功,继续向socket写入close(sock);
}
// class ThreadData{
// public:
//     int _sock;
//     std::string _ip;
//     uint16_t _port;// };class TcpServer
{
private:const static int gbacklog = 20; // 一般不能太大也不能太小// static void *threadRoutine(void *args){//     //线程分离//     pthread_detach(pthread_self());//     ThreadData *td = static_cast<ThreadData *>(args);//     service(td->_sock, td->_ip, td->_port);//     delete td;// }public:TcpServer(uint16_t port, std::string ip = "0.0.0.0"): _listensock(-1), _port(port), _ip(ip), _threadpool_ptr(ThreadPool<Task>::getThreadPool()){}void initServer(){// 1. 创建套接字 -- 进程和文件(以进程方式打开了一个文件)_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){logMessage(FATAL, "create socket error%d:%s", errno, strerror(errno));exit(2);}// 打印出创建的套接字,此处为流式logMessage(NORMAL, "create socket success, listensock: %d", _listensock);// 2. bind -- 文件 + 网络struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);// local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));exit(3);}// 3. 因为tcp是面向连接的,当正式通信的时候,需要先建立连接if (listen(_listensock, gbacklog) < 0){logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));exit(4);}logMessage(NORMAL, "init server success!");}void Start(){// signal(SIGCHLD, SIG_IGN); //主动忽略SIGCHLD信号,子进程退出的时候,自动退出僵尸状态// SIGCHLD:子进程终止或停止时,父进程收到的信号_threadpool_ptr->run(); // push任务while (true){// sleep(1);// 4. 获取链接struct sockaddr_in src;socklen_t len = sizeof(src);//(servicesock)通过每一次获得的新连接进行IO vs (_sock)j监听socket:获取新连接int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);if (servicesock < 0){// 获取连接失败logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));continue;}// 获取连接成功uint16_t client_port = ntohs(src.sin_port);std::string client_ip = inet_ntoa(src.sin_addr);logMessage(NORMAL, "link success, servicesock: %d | %s : %d | \n",servicesock, client_ip.c_str(), client_port);// 开始进行通信服务// version4 --线程池版本Task t(servicesock, client_ip, client_port, dictOnline);_threadpool_ptr->pushTask(t);// version3 --多线程版本// ThreadData *td = new ThreadData();// td->_sock = servicesock;// td->_ip = client_ip;// td->_port = client_port;// pthread_t tid;// pthread_create(&tid, nullptr, threadRoutine, td);// version2.1 --多进程版,不使用signal// pid_t id = fork();// if(id == 0){//     //child//     close(_listensock);//     if(fork() > 0) exit(0); //子进程本身立即退出//     //孙子进程被os接管//     service(servicesock, client_ip, client_port);//     exit(0);// }// //parent// waitpid(id, nullptr, 0); //因为上面的子进程直接退出,所以不会造成阻塞// close(servicesock);// version 1 -- 单进程循环版 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个// 很显然,是不能够直接被使用的// service(servicesock, c lient_ip, client_port);// version 2 -- 多进程版 -- 创建子进程// 让子进程给新的连接提供服务,子进程能打开父进程曾经打开的文件fd//  pid_t id = fork();//  assert(id != -1);//  if(id == 0){//      //子进程//      //子进程会继承父进程打开的文件与文件fd,因此此时需要关闭不需要的套接字//      close(_listensock);//      service(servicesock, client_ip, client_port);//      exit(0);//  }// 父进程//  close(servicesock); //不关闭会造成文件描述符泄漏}}~TcpServer() {}private:uint16_t _port;std::string _ip;int _listensock;std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};

tcp_client.cc

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>static void usage(std::string proc)
{std::cout << "\nUsage: " << proc << " serverIP serverPort\n"<< std::endl;
}// ./tcp_client targetIp targetPort
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);bool alive = false;int sock = 0;std::string line;while (true){if (!alive){sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(2);}// 此处客户端依然不需要bind,不需要显示的bind,但是一定是需要port// 需要让OS自动进行port选择// 要有连接别人的能力struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0){std::cerr << "connect error" << std::endl;exit(3);}std::cout << "connect success" << std::endl;alive = true;}std::cout << "请输入# ";std::getline(std::cin, line);if (line == "quit")break;ssize_t s = send(sock, line.c_str(), line.size(), 0);if (s > 0){char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s] = 0;std::cout << "server 回显# " << buffer << std::endl;}else if (s == 0){alive = false;close(sock);}}else{alive = false;close(sock);}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/732.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM内存结构—— 程序计数器,虚拟机栈 解析

JVM的内存结构 1. 程序计数器(PC Register )寄存器 1.1 全称:Program Counter Register 1.2 作用 首先,java源代码 被 编译成 二进制的 字节码 (jvm指令) jvm跨平台就是这一套指令,linux 下,windows下指令都是一致的 指令 经过 解释器 把每一条指令 解释成 机器码…

SpringBoot项目从0到1配置logback日志打印

大家好&#xff01;我是sum墨&#xff0c;一个一线的底层码农&#xff0c;平时喜欢研究和思考一些技术相关的问题并整理成文&#xff0c;限于本人水平&#xff0c;如果文章和代码有表述不当之处&#xff0c;还请不吝赐教。 以下是正文&#xff01; 一、写文背景 我们在写后端…

安卓进度条:ProgressBar和Seekbar

一、ProgressBar进度条介绍 ProgressBar 是 Android 中的一个进度条控件&#xff0c;用于显示正在进行的任务的进度。它可以以水平或圆形的形式展示进度&#xff0c;并提供了多种样式和属性来满足不同的需求。 相关属性&#xff1a; android:progress&#xff1a;设置进度条的…

计数排序

计数排序 排序步骤 1、以最大值和最小值的差值加一为长度创建一个新数组 2、将索引为0对应最小值&#xff0c;索引为1对应最小值1&#xff0c;索引为2对应最小值2&#xff0c;以此类推&#xff0c;将索引对应最小值到最大值之间所有的值 3、遍历一遍&#xff0c;遇到一个数字…

计算机网络 - http协议 与 https协议(2)

前言 本篇介绍了构造http请求的的五种方式&#xff0c;简单的使用postman构造http请求&#xff0c;进一步了解https, 学习https的加密过程&#xff0c;了解对称密钥与非对称密钥对于加密是如何进行的&#xff0c;如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流…

云计算相关概念

文章目录 一、云计算的三种部署模式&#xff1a;公有云、私有云、混合云--区别和特性二、华为云&#xff1a;简介、主要业务、特点和优势、不同场景和行业中的应用三、华为云-三剑客&#xff1a;IaaS、PaaS、SaaS 一、云计算的三种部署模式&#xff1a;公有云、私有云、混合云–…

webpack插件安装

webpack插件安装 1、html-webpack-plugin插件2 、css-loader和style-loader插件3、less-load插件 1、html-webpack-plugin插件 1、下载插件 yarn add html-webpack-plugin -D2、webpack.config.js添加配置 *const HtmlWebpackPlugin require(html-webpack-plugin); const p…

SpringBoot+JWT实现单点登录解决方案

一、什么是单点登录? 单点登录是一种统一认证和授权机制&#xff0c;指在多个应用系统中&#xff0c;用户只需要登录一次就可以访问所有相互信任的系统&#xff0c;不需要重新登录验证。 单点登录一般用于互相授信的系统&#xff0c;实现单一位置登录&#xff0c;其他信任的…

机器学习技术(五)——特征工程与模型评估

机器学习技术&#xff08;五&#xff09;——特征工程与模型评估(2️⃣) 文章目录 机器学习技术&#xff08;五&#xff09;——特征工程与模型评估(:two:)二、模型评估1、Accuracy score2、Confusion matrix混淆矩阵1、多值2、二值 3、Hamming loss4、Precision, recall and F…

深度学习笔记之Transformer(八)Transformer模型架构基本介绍

机器学习笔记之Transformer——Transformer模型架构基本介绍 引言回顾&#xff1a;简单理解&#xff1a; Seq2seq \text{Seq2seq} Seq2seq模型架构与自编码器自注意力机制 Transformer \text{Transformer} Transformer架构关于架构的简单认识多头注意力机制包含掩码的多头注意力…

Elasticsearch【全文检索、倒排索引、应用场景、对比Solr、数据结构】(一)-全面详解(学习总结---从入门到深化)

目录 Elasticsearch介绍_全文检索 Elasticsearch介绍_倒排索引 Elasticsearch介绍_Elasticsearch的出现 Elasticsearch介绍_Elasticsearch应用场景 Elasticsearch介绍_Elasticsearch对比Solr Elasticsearch介绍_Elasticsearch数据结构 Elasticsearch介绍_全文检索 Elasti…

libvirt 热迁移流程及参数介绍

01 热迁移基本原理 1.1 热迁移概念 热迁移也叫在线迁移&#xff0c;是指虚拟机在开机状态下&#xff0c;且不影响虚拟机内部业务正常运行的情况下&#xff0c;从一台宿主机迁移到另外一台宿主机上的过程。 1.2 虚拟机数据传输预拷贝和后拷贝 预拷贝(pre-copy)&#xff1a; …

Windows如何恢复已删除的Word文档?

案例&#xff1a;可以恢复已删除的Word文档吗&#xff1f; “大家好&#xff0c;我遇到了一个问题&#xff0c;需要大家的帮助。昨天我编辑了一个Word文档并保存到了桌面上&#xff0c;但当我今天再次打开电脑时&#xff0c;它就不见了&#xff01;昨天工作完成后&#xff…

Sentinel 规则详解

Sentinel 规则 流控规则 flow1、QPS流控2、并发线程数流控3、流控模式4、流控效果 熔断&#xff08;降级&#xff09;规则 degrade1、慢调用比例2、异常比例3、异常数 热点规则 param-flow授权规则 authority1、应用场景2、自定义来源3、授权规则配置 系统规则 前言&#xff1a…

JMeter进行WebSocket压力测试

背景 之前两篇内容介绍了一下 WebSocket 和 SocketIO 的基础内容。之后用 Netty-SocketIO 开发了一个简单的服务端&#xff0c;支持服务端主动向客户端发送消息&#xff0c;同时也支持客户端请求&#xff0c;服务端响应方式。本文主要想了解一下服务端的性能怎么样&#xff0c;…

4.6.tensorRT基础(1)-实际模型上onnx文件的各种操作

目录 前言1. onnx1.1 读取节点1.2 修改节点1.3 替换节点1.4 删除节点1.5 修改input和output1.6 预处理的接入 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#…

【深度学习】神经网络初学者指南

一、说明 这是一篇对神经网络的泛泛而谈的文章&#xff0c;我的意见是&#xff0c;先知道框架&#xff0c;而后知道每一个细节&#xff0c;这是学习人工智能的基本路线。本文就神经网络而言&#xff0c;谈到一些基础概念&#xff0c;适应于初学者建立概念。 二、神经网络定义 神…

计算机网络————网络层

文章目录 网络层设计思路IP地址IP地址分类IP地址与硬件地址 协议ARP和RARPIP划分子网和构造超网划分子网构造超网&#xff08;无分类编址CIDR&#xff09; ICMP 虚拟专用网VPN和网络地址转换NATVPNNAT 网络层设计思路 网络层向上只提供简单灵活的、无连接的、尽最大努力交付的数…

【MQTT】Esp32数据上传采集:最新mqtt插件(支持掉线、真机调试错误等问题)

前言 这是我在Dcloud发布的插件-最完整Mqtt示例代码&#xff08;解决掉线、真机调试错误等问题&#xff09;&#xff0c;经过整改优化和替换Mqtt的js文件使一些市场上出现的问题得以解决&#xff0c;至于跨端出问题&#xff0c;可能原因有很多&#xff0c;例如&#xff0c;合法…

<数据结构>并查集

目录 并查集概念 合并 查找集合的数量 并查集类代码实现 并查集概念 并查集和堆一样&#xff0c;都是通过数组来实现树的节点映射&#xff0c;不过并查集作用是&#xff0c;把一堆数据分为不同的几个小集合 不过并查集是森林的概念&#xff0c;并查集的学习可以帮助我们去更…