佛山便宜网站建设,网站建设外包服务,网站 备案 时间,wordpress简约新闻自媒体主题目录 网络编程 套接字#xff08;socket#xff09;1. 认识端口号2. TCP协议3. UDP协议4. 网络字节序列5. 常见的套接字6. socket编程接口6.1 socket常见APIsocket函数recvfrom函数sendto函数read函数 从tcp socket中读取接收数据 6.2 sockaddr结构6.3 地址转换函数6.4 udp s… 目录 网络编程 套接字socket1. 认识端口号2. TCP协议3. UDP协议4. 网络字节序列5. 常见的套接字6. socket编程接口6.1 socket常见APIsocket函数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_IPSRC_PORT - 套接字因此称为套接字编程。
1. 认识端口号
端口号标识特定主机上的网络进程的唯一性。
端口号port是传输层协议的内容。 端口号是一个2字节16位的整数端口号用来标识一个进程告诉操作系统当前的这个数据要交给哪一个进程来处理IP地址端口号能够标识网络上的某一台主机的某一个进程一个端口号只能被一个进程占用一个进程可以绑定多个端口号。 不是所有的进程都需要端口号。
2. TCP协议
TCPTransmission Control Protocol 传输控制协议 传输层协议有连接可靠传输面向字节流 3. UDP协议
UDPUser Datagram Protocol 用户数据报协议 传输层协议无连接不可靠传输面向数据报 4. 网络字节序列 TCP/IP协议规定网络数据流应采用大端字节序即低地址高字节。
为使网络程序具有可移植性使同样的C代码在大端和小端计算机上编译后都能正常运行可以调用以下库函数做网络字节序和主机字节序的转换。
#include arpa/inet.hunit32_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表示hostn表示networkl表示32位长整数s表示16位短整数。如果主机是小端字节序这些函数将参数做相应的大小端转换然后返回。如果主机是大端字节序这些函数不做转换将参数原封不动地返回。 5. 常见的套接字 域间socket 本主机内进程间通信也被称为一种基于套接字接口的管道通信策略原始socket不常见通常用来编写工具允许绕传输层直接使用网络层或绕过其他直接使用底层网络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。 domain指定协议族常用的有AF_INETIPv4和AF_INET6IPv6。网络通信or本地通信type套接字类型常用的有SOCK_STREAM流套接字提供可靠的、面向连接的通信和SOCK_DGRAM数据报套接字提供不可靠的、无连接的通信。protocol指定协议一般为0表示根据domain和type自动选择合适的协议。 recvfrom函数
recvfrom函数是用于接收数据的系统调用函数它从udp套接字接收数据并将其存储到指定的缓冲区中。recvfrom函数的返回值是一个整数表示接收到的数据的字节数。 如果返回值大于0表示成功接收到数据并返回接收到的字节数。如果返回值为0表示对方已经关闭了连接。如果返回值为-1表示接收数据出现错误可以通过检查errno来获取具体的错误原因。 sendto函数
发送数据到指定的目标地址 注意这里的addrlen不是指针所以传的时候不用传地址。 read函数 从tcp socket中读取接收数据 同理在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地址。 in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。 6.3 地址转换函数 字符串转in_addr in_addr 转字符串 其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。 6.4 udp socket实例 下面是一个简单的echo server例子。echo serverclient给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 ip0.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 serverclient给我们发送消息我们原封不动返回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;
};
#endifudp_server.cc
#include udp_server.hpp
#include memorystatic 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_ptrUdpServer svr(new UdpServer(port, ip));svr-initServer();svr-Start();return 0;
}完善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.hstatic 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;
}测试结果 以上就实现了本地间的通信。 通过netstat查看网络连接等信息。 本地回环地址 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 来测试和调试网络应用程序或者在本地搭建服务器和客户端进行通信。 通过popen模拟shellclient发出命令server处理并返回执行结果 void Start(){//作为一款网络服务器永远不退出的所以是一个死循环//服务器启动 - 进程 - 常驻进程 - 永远在内存中存在除非挂了/宕机//echo serverclient给我们发送消息我们原封不动返回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);}}
} 使用多线程实现客户端之间通讯 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.hppuint16_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_ptrThread sender(new Thread(1, udpSend, (void*)sock));std::unique_ptrThread 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 serverclient给我们发送消息我们原封不动返回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));}}
}注意 云服务器无法直接绑定公网IP或 非127.0.0.1、 非0.0.0.0这样的IP即云服务器无法绑定公网IP。 对于服务器来讲不推荐绑定一个确定的IP推荐使用绑定任意IP的方案。 即让服务器在工作过程中可以从指定端口中的任意IP中获取数据。 无论是多线程读还是写用的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_mapstd::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_castThreadData *(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(ThreadPoolTask::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_ptrThreadPoolTask _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.hstatic 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;
}