当前位置: 首页 > news >正文

网站制作职责怎么做正规网站吗

网站制作职责,怎么做正规网站吗,潍坊高新区建设局网站,农业服务网站建设方案Linux知识点 – 网络编程套接字 文章目录 Linux知识点 -- 网络编程套接字一、预备知识1.认识端口号2.套接字3.TCP协议与UDP协议4.网络字节序 二、socket编程接口1.socket常见API2.sockaddr结构 三、UDP套接字编程1.直接打印客户端信息2.执行客户端发来的指令3.多用户聊天4.在wi…Linux知识点 – 网络编程套接字 文章目录 Linux知识点 -- 网络编程套接字一、预备知识1.认识端口号2.套接字3.TCP协议与UDP协议4.网络字节序 二、socket编程接口1.socket常见API2.sockaddr结构 三、UDP套接字编程1.直接打印客户端信息2.执行客户端发来的指令3.多用户聊天4.在windows环境下运行客户端与云服务器下的Linux服务端通信 四、TCP套接字1.打印客户端信息并发回2.多线程版服务器 五、关于地址转换函数1.字符串转in_addr的函数2.in_addr转字符串的函数 六、TCP协议通讯流程 一、预备知识 1.认识端口号 端口号(port)是传输层协议的内容 端口号是一个2字节16位的整数端口号用来标识一个进程告诉操作系统当前的这个数据要交给哪一个进程来处理IP地址端口号能够标识网络上的某一台主机的某一个进程一个端口号只能被一个进程占用源端口号就是发送数据的端口目的端口号就是接收数据的端口 注一个进程可以绑定多个端口号但是一个端口号不能被多个进程绑定 在平常使用的APP上客户端软件发出的请求通过网络传输到服务端软件进行处理 真正的网络间通信本质其实是进程间通信 将数据在主机间转发仅仅是手段机器收到之后需要将数据交付给指定的进程 2.套接字 IP地址 端口号就是套接字 3.TCP协议与UDP协议 TCP协议 传输层协议有连接可理解为打电话对方必须响应可靠传输为保证可靠性需要更多的策略面向字节流 UDP协议 传输层协议无连接写信或发邮件不可靠传输使用成本更低适合直播、视频网站面向数据报 4.网络字节序 内存中的数据存储分大端和小端因此网络通信中不同的主机也会有不同的大小端主机之间通信 网络规定所有网络数据都必须是大端 网络字节序和主机字节序的转换库函数 其中 h表示hostn表示networkI表示32位长整数s表示16位短整数例如htonI表示将32位的长整数从主机字节序转换为网络字节序例如将IP地址转换后准备发送如果主机是小端字节序这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序这些函数不做转换将参数原封不动地返回 二、socket编程接口 1.socket常见API 2.sockaddr结构 socket API是一层抽象的网络编程接口适用于各种底层网络协议如IPv4、IPv6以及后面要讲的UNIX Domain Socket然而各种网络协议的地址格式并不相同 常见的套接字类型 域间socket原始socket网络socket 理论上是三种场景对应三套接口 但实际上所有的地址接口都是统一的 所有接口传入的都是struct sockaddr这个类型的地址参数 网络和域间套接字的前两个字节是不同的在函数内部会进行判断 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结构体指针做为参数; 三、UDP套接字编程 1.直接打印客户端信息 udp_server.hpp socket接口创建套接字 创建套接字建立一个通信的一端 创建成功返回文件描述符 但是UDP是非字节流的操作不能使用之前的文件接口访问失败返回-1并设置errno domain域套接字的类型 AF_INET是网络通信 AF_LOCAL是本地通信 type套接字通信种类UDP是面向数据报使用SOCK_DGRAM SOCK_DGRAM用户数据报 protocol前两个参数确定这个参数也确定了一般写为0 bind接口绑定套接字 将用户设置的ip和port在内核中和我们当前的进程强关联 成功返回0失败返回-1设置errno sockfd套接字 sockaddr通用addr结构AF_INET网络通信使用sockaddr_in地址结构 要显示出来sockaddr_in类型需包含头文件 sockaddr_in结构体 其中sin_port为端口号sin_addr为ip地址还需要设置一个sin_family成员与套接字的类型一致 IP地址格式转换 “192.168.110.132” - 点分十进制字符串风格的IP地址每一个区域取值范围是[0-255]: 1字节 - 4个区域 理论上表示一个IP地址其实4字节就够了 需要将点分十进制字符串风格的IP地址 - 4字节 Sin_addr其实是一个整数类型 bzero接口将指定长度的空间全部置0 先要将点分十进制字符串风格的IP地址 - 4字节再将4字节主机序列 - 网络序列 有一套接口可以一次帮我们做完这两件事情, 让服务器在工作过程中可以从任意IP中获取数据 其中inet_addr接口就是将字符串IP地址转换为网络序列IP地址 recvfrom从网络中读取数据 buf数据存储缓冲区 len缓冲区大小 flags读取方式默认0为阻塞方式 src_addr输出型参数拿到数据发送方的ip和port addrlen输入输出型参数输入: src_addr 缓冲区大小输出: 实际读到的src_addr大小 网络IP转主机IP sendto向目标主机发送数据 dest_addr目的主机地址 addrlendest_addr的大小 本地环回127.0.0.1 client和server发送数据只在本地协议栈中进行数据流动不会把我们的数据发送到网络中用于本地服务器的测试 服务器IP 云服务器无法bind公网IP对于服务器来讲也不建议绑定确定的IP INADDR_ANY宏默认为0是让服务器在工作过程中可以获取任意IP的数据 只要是发送到这个服务器的端口的数据都可以获取不再指定IP地址 服务端只需要指定端口号 #ifndef _UDP_SERVER_HPP_ #define _UDP_SERVER_HPP_#includelog.hpp #includeiostream #includeunordered_map #includecstdio #includestring #includecerrno #includecstring #includecstdlib #includestrings.h #includesys/types.h #includesys/socket.h #includenetinet/in.h #includearpa/inet.h #includeunistd.h #includequeue#define SIZE 1024class UdpServer { public:UdpServer(uint16_t port, std::string ip ): _port(port), _ip(ip), _sock(-1) // 套接字先初始化为-1{}bool initServer(){// 1.创建套接字_sock socket(AF_INET, SOCK_DGRAM, 0); // AF_NET与PF_NET是一样的if(_sock 0){logMessage(FATAL, %d:%s, errno, strerror(errno));exit(2);}//2.bind将用户设置的ip和port在内核中和我们当前的进程强关联struct sockaddr_in local; // 本地主机地址bzero(local, sizeof(local));local.sin_family AF_INET; // 与套接字类型一致// 服务器的IP和端口号未来也是要发送给对方主机的要先将数据发送到网络local.sin_port htons(_port); // 考虑大小端转换// 将主机IP转换成网络IPlocal.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if(bind(_sock, (struct sockaddr*)local, sizeof(local)) 0)// 绑定须强转成同一类型struct sockaddr*{logMessage(FATAL, %d:%s, errno, strerror(errno));exit(3);}logMessage(NORMAL, init udp server done ... %s, strerror(errno));return true;}//直接显示客户端发的消息void start(){// 服务器是永不退出的char buffer[SIZE];for(;;){struct sockaddr_in peer; // 远端地址输出型参数bzero(peer, sizeof(peer));socklen_t len sizeof(peer); // 输入输出型参数// 输入时大小为src_addr的大小// 输出时值为实际读到的dst_addr大小// 读取数据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); //输出型参数从网络中来的std::string cli_ip inet_ntoa(peer.sin_addr); // 网络4字节IP转为主机IPprintf([%s:%d]# %s\n, cli_ip.c_str(), cli_port, buffer);}//写回数据sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr*)peer, len);}}~UdpServer(){if(_sock 0){close(_sock); // 析构关闭套接字}}private://一个服务器一般需要ip地址和port16位整数uint16_t _port;std::string _ip;int _sock; // 套接字 };#endifudp_client.cc #include iostream #include string #include cstring #include unistd.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.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要但是一般client不会显示的bind程序员不会自己bind// client是一个客户端 - 普通人下载安装启动使用的- 如果程序员自己bind了-// client 一定bind了一个固定的ip和port万一其他的客户端提前占用了这个port呢// 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);if(message quit) break;//发送消息//当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; }client不需要bind特定的套接字 client是一个客户端 - 普通人下载安装启动使用的- 如果程序员自己bind了-一定bind了一个固定的ip和port万一其他的客户端提前占用了这个port呢 client一般不需要显示的bind指定port而是让OS自动随机选择 当client首次发送消息给服务器的时候OS会自动给client bind服务器的IP和port udp_server.cc #includeudp_server.hpp #includememory #includecstdlibstatic void usage(std::string proc) {std::cout \nUsage: proc port\n std::endl; }int main(int argc, char* argv[]) {if(argc ! 2){usage(argv[0]);exit(1);}uint16_t port atoi(argv[1]);std::unique_ptrUdpServer svr(new UdpServer(port)); // 使用智能指针管理对象svr-initServer();svr-start();return 0; }服务器不需要指定客户的IP地址获取数据 让服务器再工作过程中可以获取任意IP的数据 只要是发送到这个服务器的端口的数据都可以获取不再指定IP地址 服务端只需要指定端口号 Log.hpp #pragma once#include iostream #include cstdio #include cstdarg #include ctime #include string// 日志级别 #define DEBUG 0 #define NORMAL 1 #define WARNING 2 #define ERROR 3 #define FATAL 4const char *gLevelMap[] {DEBUG,NORMAL,WARNING,ERROR,FATAL };#define LOGFILE ./threadpool.log// 完整的日志功能至少需要日志等级 时间 支持用户自定义日志内容文件行文件名void logMessage(int level, const char *format, ...) { #ifndef DEBUG_SHOWif (level DEBUG)return; #endifchar stdBuffer[1024]; // 标准部分time_t timestamp time(nullptr);snprintf(stdBuffer, sizeof(stdBuffer), [%s] [%ld] , gLevelMap[level], timestamp);char logBuffer[1024]; // 自定义部分va_list args;va_start(args, format);vsnprintf(logBuffer, sizeof(logBuffer), format, args);va_end(args);// FILE *fp fopen(LOGFILE, a);// fprintf(fp, %s %s\n, stdBuffer, logBuffer);// fclose(fp);printf(%s %s\n, stdBuffer, logBuffer); }上面的代码实现了服务器直接打印客户端发送的信息并将信息在发回客户端 netstat -anup指令查看当前网络中的UDP协议服务器状态 运行结果 2.执行客户端发来的指令 popen接口可以执行传入的字符串command 执行command创建pipe管道进行进程间通信fork子进程执行execcommand命令 FILE可以将执行成果通过FILE指针进行读取* udp_server.hpp中的start成员函数 // 执行客户端指令void start(){// 服务器是永不退出的char buffer[SIZE];for (;;){struct sockaddr_in peer; // 远端地址输出型参数bzero(peer, sizeof(peer));socklen_t len sizeof(peer); // 输入输出型参数// 输入时大小为src_addr的大小// 输出时值为实际读到的dst_addr大小char result[256];std::string cmd_echo;// 读取数据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)){std::string err_message bad !;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) //通过fp读取执行结果{cmd_echo result;}fclose(fp);}// 写回数据sendto(_sock, cmd_echo.c_str(), cmd_echo.size(), 0, (struct sockaddr *)peer, len);}}运行结果 3.多用户聊天 服务器将返回的消息发送给所有用户聊天室功能 udp_server.hpp中的start函数 class UdpServer { public:void start(){// 服务器是永不退出的char buffer[SIZE];for (;;){struct sockaddr_in peer; // 远端地址输出型参数bzero(peer, sizeof(peer));socklen_t len sizeof(peer); // 输入输出型参数// 输入时大小为src_addr的大小// 输出时值为实际读到的dst_addr大小char key[64]; // 保存客户端地址信息// 读取数据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); // 获取端口号std::string cli_ip inet_ntoa(peer.sin_addr); // 获取4字节ip地址snprintf(key, sizeof key, %s-%u, cli_ip.c_str(), cli_port); //格式化打印到key中logMessage(NORMAL, key: %s, key);auto it _users.find(key);if(it _users.end()) // 若用户不存在则添加用户{logMessage(NORMAL, add new user : %s, key);_users.insert({key, peer});}}for(auto iter : _users) // 将消息推送给所有用户{std::string sendMessage key;sendMessage # ;sendMessage buffer; // 发回消息的时候加上用户信息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));}}}private:// 一个服务器一般需要ip地址和port16位整数uint16_t _port;std::string _ip;int _sock; // 套接字std::unordered_mapstd::string, struct sockaddr_in _users; // 存储用户信息};客户端一边收消息一边发消息多线程 thread.hpp #pragma once#include iostream #include string #include functional #include cstdiotypedef void *(*fun_t)(void *); // 定义函数指针类型后面回调class ThreadData // 线程信息结构体 { public:void *_args;std::string _name; };class Thread { public:Thread(int num, fun_t callback, void *args): _func(callback){char nameBuffer[64];snprintf(nameBuffer, sizeof(nameBuffer), Thread-%d, num);_name nameBuffer;_tdata._args args;_tdata._name _name;}void start() // 创建线程{pthread_create(_tid, nullptr, _func, (void *)_tdata); // 直接将_tdata作为参数传给回调函数}void join() // 线程等待{pthread_join(_tid, nullptr);}std::string name(){return _name;}~Thread(){}private:std::string _name;fun_t _func;ThreadData _tdata;pthread_t _tid; };udp_client.cc #include iostream #include string #include cstring #include unistd.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include memory #include thread.hppuint16_t server_port 0; std::string server_ip;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(server_port);server.sin_addr.s_addr inet_addr(server_ip.c_str());while (true){std::cerr 请输入你的信息# ; // 标准错误fd 2打印std::getline(std::cin, message);if (message quit){break;}sendto(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){memset(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);}server_ip argv[1];server_port atoi(argv[2]);// client要不要bind要但是一般client不会显示的bind程序员不会自己bind// client是一个客户端 - 普通人下载安装启动使用的- 如果程序员自己bind了-// client 一定bind了一个固定的ip和port万一其他的客户端提前占用了这个port呢// client一般不需要显示的bind指定port而是让OS自动随机选择(什么时候做的呢)std::unique_ptrThread sender(new Thread(1, udpSend, (void *)sock)); // 发送消息线程std::unique_ptrThread recver(new Thread(2, udpRecv, (void *)sock)); // 接收消息线程sender-start();recver-start();sender-join();recver-join();close(sock);return 0; }运行结果 通过创建管道文件让客户端发送信息和接收信息的会话分开 4.在windows环境下运行客户端与云服务器下的Linux服务端通信 windows下的套接字编程与Linux下的大致接口相同只需要添加一些windows下独有的语句就可以 udpClient.cpp #pragma warning(disable:4996) //禁用报错 #include WinSock2.h //win套接字头文件 #include iostream #include string using namespace std; #pragma comment(lib,ws2_32.lib) //固定用法引入win下的套接字库 uint16_t serverport 8080; std::string serverip 120.78.126.148; //云服务器公网ip int main() {// windows 独有的WSADATA WSAData;WORD sockVersion MAKEWORD(2, 2); //选中库if (WSAStartup(sockVersion, WSAData) ! 0)return 0;SOCKET clientSocket socket(AF_INET, SOCK_DGRAM, 0);if (INVALID_SOCKET clientSocket){cout socket error!;return 0;}sockaddr_in dstAddr;dstAddr.sin_family AF_INET;dstAddr.sin_port htons(serverport);dstAddr.sin_addr.S_un.S_addr inet_addr(serverip.c_str());char buffer[1024];while (true){std::string message;std::cout 请输入# ;std::getline(std::cin, message);sendto(clientSocket, message.c_str(), (int)message.size(), 0, (sockaddr*)dstAddr, sizeof(dstAddr));struct sockaddr_in temp;int len sizeof(temp);int s recvfrom(clientSocket, buffer, sizeof buffer, 0, (sockaddr*)temp, len);if (s 0){buffer[s] \0;std::cout server echo# buffer std::endl;}}// windows 独有closesocket(clientSocket);WSACleanup();return 0; }运行结果 四、TCP套接字 1.打印客户端信息并发回 1tcp_server.hpp 套接字类型 由于TCP协议是面向字节流的协议因此套接字的类型需选择SOCK_STREAM 面向连接的协议 因为TCP协议是面向连接的当我们正式通信的时候需要先建立连接 将套接字状态设置为监听状态 backlog全链接队列长度 成功返回0失败返回-1并设置errno netstat -antp指令查看网络中的TCP协议服务器 获取连接 accept接口获取与客户端的连接 addr输出型参数拿到客户端的ip和端口号 addlen输入输出型参数输入服务器addr大小输出客户端addr大小; 返回值成功返回套接字这是真正进行IO服务的套接字失败返回-1 传入的sockfd参数那个套接字只是获取底层的连接是监听套接字 读取消息 TCP流式套接字可以直接使用read和write接口recvfrom专用于UDP数据报读取; 单进程循环处理 一次处理一个客户端处理完了一个才能处理下一个 #pragma once #include iostream #include string #include cstring #include cerrno #include cassert #include signal.h #include unistd.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include log.hpp// 打印服务 static void service(int sock, const std::string cli_ip, const uint16_t cli_port) {// 读取消息TCP流式套接字可以直接使用read和write接口recvfrom专用于UDP数据报读取char buffer[1024];while (true){ssize_t s read(sock, buffer, sizeof(buffer - 1));if (s 0){buffer[s] 0;std::cout cli_ip : cli_port # buffer std::endl;}else if (s 0) // 对端关闭连接{logMessage(NORMAL, %s:%d shutdown, me too!, cli_ip.c_str(), cli_port);break;}else{logMessage(ERROR, read sock error, %d:%s, errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));} }class TcpServer { private:const static int gbacklog 20;public:TcpServer(uint16_t port, std::string ip ): _port(port), _ip(ip){}void initServer(){// 1.创建套接字socket -- 进程和文件_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);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(){while (true){// 4.获取连接struct sockaddr_in src; // 输出型参数获取对方主机地址socklen_t len sizeof src; // 输入输出型参数对方主机地址的长度int servicesock accept(_listensock, (struct sockaddr *)src, len); // 得到真正进行IO服务的套接字if (servicesock 0){logMessage(ERROR, accept error, %d:%s, errno, strerror(errno));continue; // 获取连接失败后继续获取}// 获取连接成功了通信对象的地址在accept函数的后两个参数中uint16_t cli_port ntohs(src.sin_port);std::string cli_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link succsee, servicesock: %d | %s : %d |\n,servicesock, cli_ip.c_str(), cli_port);// 开始进行通信服务// 单进程循环版 -- 一次处理一个客户端处理完一个才能处理下一个service(servicesock, cli_ip, cli_port);close(servicesock);}}~TcpServer(){}private:uint16_t _port;std::string _ip;int _listensock; // 监听套接字 };2tcp_server.cc #include tcp_server.hpp #include memory static void usage(std::string proc) {std::cout \nUsage: proc port\n std::endl; } // ./tcp_server port int main(int argc, char *argv[]) {if(argc ! 2){usage(argv[0]);exit(1);}uint16_t port atoi(argv[1]);std::unique_ptrTcpServer svr(new TcpServer(port));svr-initServer();svr-start();return 0; }使用远程登陆工具telnet模拟客户端 连接服务端 按照提示按下ctrl ]进入telnet 运行结果 如果创建两个客户端 服务端只会响应第一个客户端只有当第一个客户端退出时第二个客户端才会被响应 3多进程版服务器 tcp_server.hpp中的start成员函数 **父进程waitpid会阻塞进程和单进程就没区别了 方案一 可以主动忽略SIGCHID信号让子进程自动释放僵尸状态 父进程关闭servicesock因为每个进程可用的文件描述符都是有限的这里关闭了还有子进程的fd指向文件因此不会有问题 如果不关闭就会导致文件描述符泄露 void start(){signal(SIGCHLD, SIG_IGN); // 对于SIGCHLD主动忽略SIGCHLD信号子进程退出的时候会自动释放自己的僵尸状态while (true){// 4.获取连接struct sockaddr_in src; // 输出型参数获取对方主机地址socklen_t len sizeof src; // 输入输出型参数对方主机地址的长度int servicesock accept(_listensock, (struct sockaddr *)src, len); // 得到真正进行IO服务的套接字if (servicesock 0){logMessage(ERROR, accept error, %d:%s, errno, strerror(errno));continue; // 获取连接失败后继续获取}// 获取连接成功了通信对象的地址在accept函数的后两个参数中uint16_t cli_port ntohs(src.sin_port);std::string cli_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link succsee, servicesock: %d | %s : %d |\n,servicesock, cli_ip.c_str(), cli_port);// 开始进行通信服务//多进程版 -- 创建子进程//让子进程给新的连接提供服务子进程是能够直接打开父进程曾经打开的文件fd的pid_t id fork();assert(id ! -1);if(id 0){//子进程能够继承父进程的文件fd//子进程是来提供服务的不需要知道监听socketclose(_listensock);//关闭不需要的文件描述符service(servicesock, cli_ip, cli_port);exit(0); // 使用忽略SIGCHLD信号来自动退出僵尸状态}close(servicesock);}}运行结果 可以进行多客户端通信了 方案二不忽略SIGCHLD信号 在子进程中再fork创建孙子进程让孙子进程执行service子进程立马退出 孙子进程就变成孤儿进程让OS领养OS在孤儿进程退出的时候由OS自动回收孤儿进程 void start(){while (true){// 4.获取连接struct sockaddr_in src; // 输出型参数获取对方主机地址socklen_t len sizeof src; // 输入输出型参数对方主机地址的长度int servicesock accept(_listensock, (struct sockaddr *)src, len); // 得到真正进行IO服务的套接字if (servicesock 0){logMessage(ERROR, accept error, %d:%s, errno, strerror(errno));continue; // 获取连接失败后继续获取}// 获取连接成功了通信对象的地址在accept函数的后两个参数中uint16_t cli_port ntohs(src.sin_port);std::string cli_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link succsee, servicesock: %d | %s : %d |\n,servicesock, cli_ip.c_str(), cli_port);// 多进程版 -- 创建子进程// 让子进程给新的连接提供服务子进程是能够直接打开父进程曾经打开的文件fd的pid_t id fork();assert(id ! -1);// 多进程版 -- 创建孙子进程if (id 0){// 子进程能够继承父进程的文件fd// 子进程是来提供服务的不需要知道监听socketclose(_listensock); // 关闭不需要的文件描述符if(fork() 0){exit(0);// 子进程调用fork创建孙子进程然后子进程退出}// 孙子进程在子进程退出后编程孤儿进程OS领养OS在孤儿进程退出后自动回收service(servicesock, cli_ip, cli_port);exit(0); // 使用忽略SIGCHLD信号来自动退出僵尸状态}waitpid(id, nullptr, 0); // 子进程及时退出不会阻塞等待close(servicesock);}}运行结果 4客户端 TCP的服务端需要bind一定需要一个确定的port TCP的客户端不需要bind一旦bind就说明客户端绑定的是一个具体端口号两个客户端可能是由不同公司写的可能端口号会出现冲突 需要让操作系统自动进行port选择 客户端需要连接别人 connect接口连接特定的IP和port 后两个参数与sendto参数相同目的主机的地址和地址的大小 成功返回0失败返回-1 send接口tcp发送接口 flags默认为0 recv接口tcp接收接口 注send和recv也可以使用read和write代替 tcp_client.cc #include tcp_server.hpp #include iostream #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; }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);}// client不需要显式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 l 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; }运行结果 2.多线程版服务器 1每次连接时都创建新的线程 tcp_server.hpp 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;return nullptr;}public:TcpServer(uint16_t port, std::string ip ): _port(port), _ip(ip){}void initServer(){// 1.创建套接字socket -- 进程和文件_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);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(){while (true){// 4.获取连接struct sockaddr_in src; // 输出型参数获取对方主机地址socklen_t len sizeof src; // 输入输出型参数对方主机地址的长度int servicesock accept(_listensock, (struct sockaddr *)src, len); // 得到真正进行IO服务的套接字if (servicesock 0){logMessage(ERROR, accept error, %d:%s, errno, strerror(errno));continue; // 获取连接失败后继续获取}// 获取连接成功了通信对象的地址在accept函数的后两个参数中uint16_t cli_port ntohs(src.sin_port);std::string cli_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link succsee, servicesock: %d | %s : %d |\n,servicesock, cli_ip.c_str(), cli_port);// 多线程ThreadData* td new ThreadData();td-_sock servicesock;td-_ip cli_ip;td-_port cli_port;pthread_t tid;// 多线程不需要关闭文件描述符因为多线程共享文件描述符pthread_create(tid, nullptr, threadRoutine, td);}}~TcpServer(){}private:uint16_t _port;std::string _ip;int _listensock; // 监听套接字 }; 运行结果 2使用线程池管理线程 引入之前写的线程池具体代码见Linux知识点 – Linux多线程四 线程池代码中 Task.hpp 更改了回调函数的类型 #pragma once#include iostream #include string #include functional #include log.hpp//typedef std::functionvoid (int, const std::string, const uint16_t, const std::string) func_t;//与上面的写法是等价的 using func_t std::functionvoid (int, const std::string, const uint16_t, const std::string);class Task {public:Task() {}Task(int sock, const std::string ip, uint16_t port, func_t func) : _sock(sock), _ip(ip), _port(port), _func(func){}void operator()(const std::string name){_func(_sock, _ip, _port, name);}public:int _sock;std::string _ip;uint16_t _port;func_t _func; };tcp_server.hpp 更改了service回调函数和类内成员函数start每次将任务push进任务队列等待线程池处理 #pragma once #include iostream #include string #include unordered_map #include cstring #include cerrno #include cassert #include signal.h #include unistd.h #include memory #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include sys/wait.h #include pthread.h #include ctype.h #include thread-pool/log.hpp #include thread-pool/threadPool.hpp #include thread-pool/Task.hppstatic void service(int sock, const std::string cli_ip,const uint16_t cli_port, const std::string thread_name) {// 读取消息TCP流式套接字可以直接使用read和write接口recvfrom专用于UDP数据报读取char buffer[1024];while (true){ssize_t s read(sock, buffer, sizeof(buffer - 1));if (s 0){buffer[s] 0;std::cout thread_name | cli_ip : cli_port # buffer std::endl;}else if (s 0) // 对端关闭连接{logMessage(NORMAL, %s:%d shutdown, me too!, cli_ip.c_str(), cli_port);break;}else{logMessage(ERROR, read sock error, %d:%s, errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock); // 线程在回调函数中关闭不用的文件描述符 }class TcpServer { private:const static int gbacklog 20;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.创建套接字socket -- 进程和文件_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);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(){_threadpool_ptr-run(); // 启动线程池while (true){// 4.获取连接struct sockaddr_in src; // 输出型参数获取对方主机地址socklen_t len sizeof src; // 输入输出型参数对方主机地址的长度int servicesock accept(_listensock, (struct sockaddr *)src, len); // 得到真正进行IO服务的套接字if (servicesock 0){logMessage(ERROR, accept error, %d:%s, errno, strerror(errno));continue; // 获取连接失败后继续获取}// 获取连接成功了通信对象的地址在accept函数的后两个参数中uint16_t cli_port ntohs(src.sin_port);std::string cli_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link succsee, servicesock: %d | %s : %d |\n,servicesock, cli_ip.c_str(), cli_port);// 线程池版本Task t(servicesock, cli_ip, cli_port, service);_threadpool_ptr-pushTask(t);}}~TcpServer(){}private:uint16_t _port;std::string _ip;int _listensock; // 监听套接字std::unique_ptrThreadPoolTask _threadpool_ptr; // 线程池指针 }; 其他代码都与之前的一致 运行结果 也可以传入其他的回调函数完成不同的功能 五、关于地址转换函数 1.字符串转in_addr的函数 inet_aton地址转换函数: inet_pton地址转换函数 2.in_addr转字符串的函数 关于inet_ntoa 因为inet_ntoa把结果放到自己内部的一个静态存储区这样第二次调用时的结果会覆盖掉上一次的结果因此inet_ntoa是线程不安全的 在多线程环境下推荐使用inet_ ntop这个函数由调用者提供一个缓冲区保存结果可以规避线程安全问题 六、TCP协议通讯流程 服务器初始化 调用socket,创建文件描述符 调用bind,将当前的文件描述符和ip/port绑定在一起如果这个端口已经被其他进程占用了就会bind失败 调用listen声明当前这个文件描述符作为一个服务器的文件描述符为后面的accept做好准备 调用accecpt并阻塞等待客户端连接过来 建立连接的过程三次握手 调用socket创建文件描述符 调用connect向服务器发起连接请求 connect会发出SYN段并阻塞等待服务器应答(第一次) 服务器收到客户端的SYN会应答一个SYN-ACK段表示同意建立连接(第二次) 客户端收到SYN-ACK后会从connect(返回同时应答一个ACK段(第三次) 数据传输的过程 建立连接后TCP协议提供全双工的通信服务所谓全双工的意思是在同-条连接中,同一时刻通信双方可以同时写数据相对的概念叫做半双工同一条连接在同一时刻只能由一方来写数据 服务器从accept()返回后立刻调用read)读socket就像读管道一样如果没有数据到达就阻塞等待 这时客户端调用write0发送请求给服务器服务器收到后从read(返回,对客户端的请求进行处理在此期 间客户端调用read()阻塞等待服务器的应答 服务器调用write()将处理结果发回给客户端再次调用read()阻塞等待下一条请求 客户端收到后从read()返回发送下一条请求如此循环下去 断开连接的过程四次挥手 如果客户端没有更多的请求了就调用close()关闭连接客户端会向服务器发送FIN段(第一次) 此时服务器收到FIN后会回应一个ACK同时read会返回0 (第二次) read返回之后服务器就知道客户端关闭了连接也调用close关闭连接这个时候服务器会向客户端发送一个FIN(第三次) 客户端收到FIN再返回一个ACK给服务器(第四次)
文章转载自:
http://www.morning.qsszq.cn.gov.cn.qsszq.cn
http://www.morning.ckbmz.cn.gov.cn.ckbmz.cn
http://www.morning.mwbqk.cn.gov.cn.mwbqk.cn
http://www.morning.xldpm.cn.gov.cn.xldpm.cn
http://www.morning.rgmls.cn.gov.cn.rgmls.cn
http://www.morning.qgjgsds.com.cn.gov.cn.qgjgsds.com.cn
http://www.morning.rhkgz.cn.gov.cn.rhkgz.cn
http://www.morning.jbshh.cn.gov.cn.jbshh.cn
http://www.morning.sjpbh.cn.gov.cn.sjpbh.cn
http://www.morning.cxryx.cn.gov.cn.cxryx.cn
http://www.morning.dmchips.com.gov.cn.dmchips.com
http://www.morning.bojkosvit.com.gov.cn.bojkosvit.com
http://www.morning.dmwck.cn.gov.cn.dmwck.cn
http://www.morning.jpydf.cn.gov.cn.jpydf.cn
http://www.morning.mqxrx.cn.gov.cn.mqxrx.cn
http://www.morning.rmkyb.cn.gov.cn.rmkyb.cn
http://www.morning.qqklk.cn.gov.cn.qqklk.cn
http://www.morning.rycd.cn.gov.cn.rycd.cn
http://www.morning.ryxbz.cn.gov.cn.ryxbz.cn
http://www.morning.rzsxb.cn.gov.cn.rzsxb.cn
http://www.morning.wgzzj.cn.gov.cn.wgzzj.cn
http://www.morning.bgrsr.cn.gov.cn.bgrsr.cn
http://www.morning.qzzmc.cn.gov.cn.qzzmc.cn
http://www.morning.rkfh.cn.gov.cn.rkfh.cn
http://www.morning.drspc.cn.gov.cn.drspc.cn
http://www.morning.kgphd.cn.gov.cn.kgphd.cn
http://www.morning.2d1bl5.cn.gov.cn.2d1bl5.cn
http://www.morning.lwzgn.cn.gov.cn.lwzgn.cn
http://www.morning.mfltz.cn.gov.cn.mfltz.cn
http://www.morning.0dirty.cn.gov.cn.0dirty.cn
http://www.morning.cnhgc.cn.gov.cn.cnhgc.cn
http://www.morning.wpydf.cn.gov.cn.wpydf.cn
http://www.morning.xhgxd.cn.gov.cn.xhgxd.cn
http://www.morning.qpmwb.cn.gov.cn.qpmwb.cn
http://www.morning.mkpkz.cn.gov.cn.mkpkz.cn
http://www.morning.mfbcs.cn.gov.cn.mfbcs.cn
http://www.morning.zwfgh.cn.gov.cn.zwfgh.cn
http://www.morning.bchfp.cn.gov.cn.bchfp.cn
http://www.morning.ygwyt.cn.gov.cn.ygwyt.cn
http://www.morning.yqwsd.cn.gov.cn.yqwsd.cn
http://www.morning.zdmrf.cn.gov.cn.zdmrf.cn
http://www.morning.qckwj.cn.gov.cn.qckwj.cn
http://www.morning.rqkck.cn.gov.cn.rqkck.cn
http://www.morning.aiai201.cn.gov.cn.aiai201.cn
http://www.morning.sgbjh.cn.gov.cn.sgbjh.cn
http://www.morning.bpmnq.cn.gov.cn.bpmnq.cn
http://www.morning.nlbw.cn.gov.cn.nlbw.cn
http://www.morning.lfqnk.cn.gov.cn.lfqnk.cn
http://www.morning.rnribht.cn.gov.cn.rnribht.cn
http://www.morning.bxfy.cn.gov.cn.bxfy.cn
http://www.morning.qdlnw.cn.gov.cn.qdlnw.cn
http://www.morning.qnxkm.cn.gov.cn.qnxkm.cn
http://www.morning.dxpzt.cn.gov.cn.dxpzt.cn
http://www.morning.djwpd.cn.gov.cn.djwpd.cn
http://www.morning.knpmj.cn.gov.cn.knpmj.cn
http://www.morning.wqkfm.cn.gov.cn.wqkfm.cn
http://www.morning.sxfnf.cn.gov.cn.sxfnf.cn
http://www.morning.rycd.cn.gov.cn.rycd.cn
http://www.morning.yzsdp.cn.gov.cn.yzsdp.cn
http://www.morning.zfrs.cn.gov.cn.zfrs.cn
http://www.morning.rtqyy.cn.gov.cn.rtqyy.cn
http://www.morning.xkwrb.cn.gov.cn.xkwrb.cn
http://www.morning.zbnkt.cn.gov.cn.zbnkt.cn
http://www.morning.jsxrm.cn.gov.cn.jsxrm.cn
http://www.morning.nlnmy.cn.gov.cn.nlnmy.cn
http://www.morning.rcrnw.cn.gov.cn.rcrnw.cn
http://www.morning.lszjq.cn.gov.cn.lszjq.cn
http://www.morning.bytgy.com.gov.cn.bytgy.com
http://www.morning.yrbq.cn.gov.cn.yrbq.cn
http://www.morning.wxccm.cn.gov.cn.wxccm.cn
http://www.morning.rqnml.cn.gov.cn.rqnml.cn
http://www.morning.qbfs.cn.gov.cn.qbfs.cn
http://www.morning.hjssh.cn.gov.cn.hjssh.cn
http://www.morning.fblkr.cn.gov.cn.fblkr.cn
http://www.morning.xdqrz.cn.gov.cn.xdqrz.cn
http://www.morning.3dcb8231.cn.gov.cn.3dcb8231.cn
http://www.morning.krbjb.cn.gov.cn.krbjb.cn
http://www.morning.rnxs.cn.gov.cn.rnxs.cn
http://www.morning.hrgxk.cn.gov.cn.hrgxk.cn
http://www.morning.lmknf.cn.gov.cn.lmknf.cn
http://www.tj-hxxt.cn/news/248179.html

相关文章:

  • 利用google地图标注做网站哈尔滨建设网官方网站
  • 贞丰县住房和城乡建设局网站厦门企业app开发
  • 南宁专业网站开发制作网站免费建站
  • 外贸视频网站开发为什么做电影网站没有流量
  • 网站开发公司 上wordpress网盘搜索
  • 网上做兼职的网站frontpage做内部网站
  • 网页上传和网站开发游戏软件开发公司简介
  • 新乡网站建设制作公司淮北招聘网淮北论坛
  • developer官网下载东莞优化哪家好
  • wordpress说说插件企业网站产品内页优化
  • 做网站的桔子什么品牌推广策略案例
  • 上海兴业建设有限公司网站推荐家居网站建设
  • 音频网站建设做企业网站电话销售话术
  • 郑州好的网站建设公司教育网站制作哪家服务好
  • 南山老品牌网站建设防伪码网站怎么做
  • 公司网站的管理和维护橙色wordpress模板
  • 有哪些好的模板网站睢宁建网站
  • 如何把旅行社网站做的好看一家只做家纺的网站
  • 技术网站源码wordpress新手建什么网站赚钱
  • 凡科论坛网站制作北京做网站维护
  • 如何做一份企业网站规划广州最新新闻头条
  • 扬州公司做网站公司哪家好吗网站建设
  • 公司要做网站做免费漫画网站有风险吗
  • 网站营销网站优化开发一个app需要多少钱 知乎
  • 什么网站可以做公共基础知识企业门户网模板下载
  • js 网站开发罗湖商城网站设计电话
  • 网站开发的prd 怎么写网站建设类岗位有哪些
  • 微信小程序里的网站怎么做wordpress访客主机名
  • 漳州微信网站建设wordpress淘宝插件
  • 哇哈哈网站建设策划书注册企业邮箱哪家最好