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

企联网站建设/seo推广是什么

企联网站建设,seo推广是什么,建设网站注意事项,上海企业网站制作报价文章目录1. socket编程接口1-1 socket 常见API1-2 sockaddr结构2. 简单的UDP网络程序2-1 日志(固定用法:标准部分自定义部分)2-2 服务器代码实现1. 框架2. 初始化服务器3. 服务器运行4. 调用服务器封装函数(UdpServer)…

文章目录

  • 1. socket编程接口
    • 1-1 socket 常见API
    • 1-2 sockaddr结构
  • 2. 简单的UDP网络程序
    • 2-1 日志(固定用法:标准部分+自定义部分)
    • 2-2 服务器代码实现
      • 1. 框架
      • 2. 初始化服务器
      • 3. 服务器运行
      • 4. 调用服务器封装函数(UdpServer)
    • 2-3 客户端代码
    • 2-4 结果展示
    • 2-5 改进服务器
    • 2-6 改进用户
      • 1. 封装线程
      • 2. 创建两个线程(一个接受信息一个发送信息)
    • 2-7 改进后群聊功能展示
    • 2-8 总代码链接
  • 3. 简单的TCP网络程序
    • 3-1 日志(固定用法参考上面2.1)
    • 3-2 服务器代码实现
      • 1. 框架
      • 2. 初始化服务器
      • 3. 调用服务器封装函数(TcpServer)
      • 4. 服务器运行(铺垫)
      • 5. 服务器运行(多进程版)
      • 6. 服务器运行(多线程版)
    • 3-3 客户端代码
    • 3-4 结果展示
    • 3-5 总代码链接

1. socket编程接口

1-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);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

1-2 sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、 IPv6,以及后面要讲的UNIX Domain
Socket.

各种网络协议的地址格式并不相同;为了让接口统一我们根据前16位作区别(就好像原始模板划分为两个不同类型模块)

在这里插入图片描述

  • 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结构体指针做为参数。

sockaddr 结构

/* Structure describing a generic socket address.  */
struct sockaddr{__SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */char sa_data[14];		/* Address data.  */};

sockaddr_in 结构

/* Structure describing an Internet socket address.  */
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port;			/* Port number.  */struct in_addr sin_addr;		/* Internet address.  *//* Pad to size of `struct sockaddr'.  */unsigned char sin_zero[sizeof (struct sockaddr) -__SOCKADDR_COMMON_SIZE -sizeof (in_port_t) -sizeof (struct in_addr)];};
  • 虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

in_addr结构

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr{in_addr_t s_addr;};
  • in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

2. 简单的UDP网络程序

2-1 日志(固定用法:标准部分+自定义部分)

#pragma once#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>using std::cout;
using std::endl;
using std::string;// 日志是有日志级别的
#define DEBUG 0   //  调试
#define NORMAL 1  //  正常
#define WARNING 2 //  警告
#define ERROR 3   //  错误
#define FATAL 4   //  致命错误static const size_t BUFFER_NUM = 1024;
const char *gLevelMap[] = {"DEBUG","NORMAL","WARNING","ERROR","FATAL"};// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void LogMessage(int level, const char *format, ...)	//	可变参数模板
{char stdBuffer[BUFFER_NUM]; // 标准部分const time_t timestamp = time(nullptr);struct tm *L_Time = localtime(&timestamp);string time;time += "日期-时间:" + std::to_string(L_Time->tm_year+1900) + "/" + std::to_string(L_Time->tm_mon) + "/" + std::to_string(L_Time->tm_mday) + "-" + std::to_string(L_Time->tm_hour) + ":" + std::to_string(L_Time->tm_min) + ":" + std::to_string(L_Time->tm_sec);std::to_string(L_Time->tm_sec);snprintf(stdBuffer, sizeof stdBuffer, "[%s][%s]",gLevelMap[level], time.c_str());char logBuffer[BUFFER_NUM]; // 自定义部分va_list args;va_start(args, format);vsnprintf(logBuffer, sizeof logBuffer, format, args);va_end(args);printf("%s%s\n", stdBuffer, logBuffer);
}

2-2 服务器代码实现

1. 框架

#ifndef _UDP_SERVERHPP
#define _UDP_SERVERHPP#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include "Log.hpp"using std::cout;
using std::endl;
using std::string;static const size_t SIZE = 1024;
class UdpServer
{
public:UdpServer(uint16_t port, string ip = "") : _port(port), _ip(ip){}//  初始化bool InitServer(){}//  服务器运行void start(){}~UdpServer(){if(_sock>0) close(_sock);   //  通过文件描述符关闭文件}private:// 一个服务器,一般必须需要ip地址和port(16位的整数)uint16_t _port;string _ip;int _sock = -1; // 创建 socket 文件描述符 的返回值
};#endif

2. 初始化服务器

    1. 创建套接字
      在这里插入图片描述

固定用法AF_INET 和 SOCK_DGRAM;最后一个参数(设置成0)

    1. bind: 将用户设置的ip和port在内核中和我们当前的进程强关联
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);1)参数 sockfd ,需要绑定的socket。(文件描述符)2)参数 addr ,存放了服务端用于通信的地址和端口。(3)参数 addrlen ,表示 addr 结构体的大小(4)返回值:成功则返回0 ,失败返回-1,错误原因存于 errno 中。如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错,否则一般不会返回错误。

注意:

我们普通用户见得最多的是这种"192.168.110.132"IP地址是点分十进制字符串风格的IP地址每一个区域取值范围是[0-255]: 1字节 -> 4个区域

理论上,表示一个IP地址,其实4字节就够了(减少浪费)
所有在网络上我们需要把点分十进制字符串风格的IP地址 转为4字节再转为网络序列
不过有一套接口,可以一次帮我们做完这两件事情, 让服务器在工作过程中,可以从任意IP中获取数据(inet_addr)

用这个INADDR_ANY宏;表示bind任意IP(便于服务器接收数据)

    //  初始化bool InitServer(){// 从这里开始,就是新的系统调用,来完成网络功能啦// 1. 完成套接字创建_sock = socket(AF_INET, SOCK_DGRAM, 0);if (_sock < 0) //  创建失败{LogMessage(FATAL, "%d:%s | %s | %d", errno, strerror(errno), __FILE__, __LINE__);exit(2);}// 2. 绑定(将用户设置的ip和port在内核中和我们当前的进程强关联)struct sockaddr_in local;bzero(&local, sizeof local);   //  结构体清理(初始化)local.sin_family = AF_INET;    //    协议家族local.sin_port = htons(_port); //  网络字节序大端存储//  将点分十进制字符串风格的IP地址 ->  4字节-> 网络序列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 | %s | %d", errno, strerror(errno), __FILE__, __LINE__);exit(3);}LogMessage(NORMAL, "init udp server done ... | %s | %d", __FILE__, __LINE__);return true;}

3. 服务器运行

recvfrom () 用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socklen_t *fromlen);1)ssize_t 相当于 long int,socklen_t 相当于int2)sockfd:标识一个已连接套接口的描述字。(3)buf:接收数据缓冲区。(4)len:缓冲区长度。(5)flags:调用操作方式。是以下一个或者多个标志的组合体,可通过“ | ”操作符连在一起(通常设置为0)(6)from 是一个指向sockaddr结构体类型的指针;(7*fromlen表示my_addr结构的长度,可以用sizeof操作符获得。(8)返回值:成功则返回接收到的字符数,失败返回-1.
sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据内容,参数flags 一般设0
int sendto ( socket s , const void * msg, int len, unsigned int flags, const
struct sockaddr * to , int tolen ) ;1)s:一个用于标识已连接套接口的描述字。(2)buf:包含待发送数据的缓冲区。(3)len:缓冲区中数据的长度。(4)flags:调用执行方式。(5)to 是一个指向sockaddr结构体类型的指针;(5)参数tolen表示to结构的长度,可以用sizeof操作符获得。(6)成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。
  • 代码块:
//  服务器运行void start(){// 作为一款网络服务器,永远不退出的!// 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!char buffer[SIZE]; //  存储读取到的数据for (;;){struct sockaddr_in peer;memset(&peer, 0, sizeof peer);socklen_t len = sizeof peer;//  读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0) //   读取成功{//  谁发的数据buffer[s] = 0;                           // 我们目前数据当做字符串uint16_t cliPort = ntohs(peer.sin_port); // 从网络中来的// 4字节的网络序列的IP->本主机的字符串风格的IP,方便显示string cliIP = inet_ntoa(peer.sin_addr);printf("[%s:%d]# %s\n", cliIP.c_str(), cliPort, buffer);}// end. 写回数据sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);}}

4. 调用服务器封装函数(UdpServer)

#include "udp_server.hpp"
#include <memory>static void Usage(string proc)
{std::cout << "\nUsage: " << proc << " serverIp serverPort\n"<< std::endl;
}
// ./udp_server 127.0.0.1 8080
int main(int argc, char *argv[]) //  命令行参数
{if (argc != 3){Usage(argv[0]); // 使用手册exit(1);}std::string ip = argv[1];u_int16_t port=atoi(argv[2]);std::unique_ptr<UdpServer> server(new UdpServer(port, ip));   //  智能指针server->InitServer();server->start();return 0;
}

2-3 客户端代码

  • 注意:
1)client要bind,但是一般client不会显示的bind,程序员不会自己bind
(2)client是一个客户端 -> 普通人下载安装启动使用的-> 如果程序员自己bind了->client 一定bind了一个固定的ip和port,万一,其他的客户端提前占用了这个port; 就芭比Q了
(3)client一般不需要显示的bind指定port,而是让OS自动随机选择(具体在当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORT就是调用sendto()函数自动bind)
#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>using std::cout;
using std::endl;
using std::string;static const size_t SIZE = 1024;
static void Usage(string proc)
{std::cout << "\nUsage: " << proc << " serverIp serverPort\n"<< std::endl;
}// ./udp_client 127.0.0.1 8080
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 << "client: socket error" << endl;exit(2);}// client要要,但是一般client不会显示的bind,程序员不会自己bindstring 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[SIZE];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;memset(&temp, 0, len);ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << "server echo# " << buffer << endl;}std::cerr << "client: srecvfrom error" << endl;}close(sock);return 0;
}

2-4 结果展示

在这里插入图片描述

2-5 改进服务器

  • 我们不直接绑定服务器;达到如下效果
    在这里插入图片描述
  • 我们需要哈希桶完成映射;并存储用户信息
//  服务器运行void start(){// 作为一款网络服务器,永远不退出的!// 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!char buffer[SIZE]; //  存储读取到的数据for (;;){char key[64];struct sockaddr_in peer;memset(&peer, 0, sizeof peer);socklen_t len = sizeof peer;//  读取数据ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0) //   读取成功{//  谁发的数据buffer[s] = 0;                           // 我们目前数据当做字符串uint16_t cliPort = ntohs(peer.sin_port); // 从网络中来的// 4字节的网络序列的IP->本主机的字符串风格的IP,方便显示string cliIP = inet_ntoa(peer.sin_addr);// printf("[%s:%d]# %s\n", cliIP.c_str(), cliPort, buffer);snprintf(key, sizeof(key), "%s-%u", cliIP.c_str(), cliPort); // 127.0.0.1-8080LogMessage(NORMAL, "key: %s | %s | %d", key, __FILE__, __LINE__);auto it = _users.find(key);if (it == _users.end()){// 储存LogMessage(NORMAL, "add new user : %s | %s | %d", key, __FILE__, __LINE__);_users[key] = peer;}}// end. 写回数据for (auto &iter : _users){string sendMessage = key;sendMessage += "# ";sendMessage += buffer; // 127.0.0.1-1234# 你好LogMessage(NORMAL, "push message to %s | %s | %d", iter.first.c_str(), __FILE__, __LINE__);sendto(_sock, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second));}}}

在这里插入图片描述

2-6 改进用户

多线程跑起来;实现群聊功能

1. 封装线程

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <cstdio>using std::cout;
using std::endl;
using std::string;static const size_t NAME_NUM = 1024;
typedef void*(*fun_t)(void*);     // 线程执行的方法class ThreadDate
{
public:void *_args;string _name;
};class Thread
{
public:Thread(int num, fun_t callback, void *args) : _func(callback){char nameBuffer[NAME_NUM];snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", num);_tdata._name = nameBuffer;_tdata._args = args;}void start() //  创造线程{pthread_create(&_tid, nullptr, _func, (void *)&_tdata);}void join() //  等待线程{pthread_join(_tid, nullptr);}string &name() //  线程名字{return _tdata._name;}private:fun_t _func;ThreadDate _tdata;pthread_t _tid;
};

2. 创建两个线程(一个接受信息一个发送信息)

  • 其实稍微把发送和接受解耦;封装一下就可以了
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "thread.hpp"using std::cout;
using std::endl;
using std::string;uint16_t serverPort = 0;
string serverIp;
static const size_t SIZE = 1024;
static void Usage(string proc)
{cout << "\nUsage: " << proc << " serverIp serverPort\n"<< endl;
}static void *UdpSend(void *args) //  发送消息
{ThreadDate *td = (ThreadDate *)args;int sock = *(int *)td->_args;string name = td->_name;// client要不要bind??要,但是一般client不会显示的bind,程序员不会自己bindstring 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 << "请输入你的信息# ";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 *UdpAccept(void *args) //  接受信息
{ThreadDate *td = (ThreadDate *)args;int sock = *(int *)td->_args;string name = td->_name;char buffer[SIZE];while (true){struct sockaddr_in temp;socklen_t len = sizeof temp;memset(&temp, 0, len);ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}else{std::cerr << "client: srecvfrom error" << endl;}}return nullptr;
}// ./udp_client 127.0.0.1 8080
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 << "client: socket error" << endl;exit(2);}serverIp = argv[1];serverPort = atoi(argv[2]);std::unique_ptr<Thread> Sender(new Thread(1, UdpSend, (void *)&sock));std::unique_ptr<Thread> Accepter(new Thread(2, UdpAccept, (void *)&sock));//  创建线程Sender->start();Accepter->start();//  等待进程Sender->join();Accepter->join();close(sock); //  关闭文件(sock本质是一个文件)return 0;
}

2-7 改进后群聊功能展示

注意:

  • 首次发消息;也再做管理该用户
    在这里插入图片描述

2-8 总代码链接

链接:
https://gitee.com/ding-xushengyun/linux__cpp/commit/6bfc59b29aae02abecb88d43b1f2b7b4c071fe2c

3. 简单的TCP网络程序

3-1 日志(固定用法参考上面2.1)

3-2 服务器代码实现

1. 框架

  • 框架跟UDP几乎相同
#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cassert>
#include "Log.hpp"using std::cout;
using std::endl;
using std::string;static const size_t SIZE = 1024;
class UdpServer
{const static int gbacklog = 20;
public:UdpServer(uint16_t port, string ip = "") : _port(port), _ip(ip){}//  初始化bool InitServer(){}//  服务器运行void start(){}~UdpServer(){}private:// 一个服务器,一般必须需要ip地址和port(16位的整数)uint16_t _port;string _ip;int _listenSock = -1; //  监听套接字(建立新链接---客户)
};

2. 初始化服务器

  • 初始化:1. 创建套接字(跟上面UDP几乎相同;不同的是第二参数UDP是数据报,我们是面向字节流) 2. 绑定bind;最大不同TCP只不过多了一个聆听- - -listen
  • 因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接(就好像厨师一直在等待顾客的到来;因为我们不知道顾客什么适合来吃饭)
  • 云服务器不能绑定公有IP;需要随机绑定INADDR_ANY
int listen(int sockfd, int backlog);1)sockfd 参数表示监听的 socket 句柄
(2)backlog 参数表示接收请求队列的长度(不能太大)
(3)成功则返回 0,失败返回-1,错误原因存于errno 中。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
  • 代码块:
    void InitServer(){//  1. 创建套接字_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0) //  创建失败{LogMessage(FATAL, "创建套接字失败 %d:%s", errno, strerror(errno));exit(2);}LogMessage(FATAL, "创建套接字成功, listensock: %d", _listenSock); //  3//  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, "绑定失败, %d:%s", errno, strerror(errno));exit(3);}// 3. 因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接if (listen(_listenSock, gbacklog) < 0){LogMessage(ERROR, "建立链接失败, %d:%s", errno, strerror(errno));exit(4);}LogMessage(NORMAL, "初始化成功!!!");}

3. 调用服务器封装函数(TcpServer)

  • 跟UDP没什么不同
#include <memory>
#include "tcp_server.hpp"static void Usage(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_ptr<TcpServer> sve(new TcpServer(port));sve->InitServer();sve->start();return 0;
}

4. 服务器运行(铺垫)

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);1)sock 为服务器端套接字
(2)addr 为 sockaddr_in 结构体变量
(3)addrlen 为参数 addr 的长度,可由 sizeof() 求得。
(4)成功则返回 socket(这个sock用来提供服务的),失败返回-1,错误原因存于errno 中。
tcp 面向字节流而且sock还是文件描述符;我们可以把接受和发送数据当作文件处理(read和write)
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{char buffer[SIZE];for (;;){// read && write 可以直接被使用ssize_t s = read(sock, buffer, sizeof buffer - 1);if (s > 0){buffer[s] = 0; // 将发过来的数据当做字符串cout << clientip << ":" << clientport << "# " << buffer << endl;}else if (s == 0) // 对写端关闭连接{LogMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);break;}else{ //LogMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}
}
  • 我们链接(accept)成功后;直接调用service()函数我们得到的只是一个单进程版;什么意思呐?如下图:
    在这里插入图片描述
    (用telnet简单测试一下服务器)我们明显发现只能一个客户发消息;其它客户发消息需要等第一个客户退出才能行。

5. 服务器运行(多进程版)

怎么实现服务器并发处理客户端呐?

  • 我们需要让子进程提供服务(调用service函数);父进程监听socket
  • 子进程继承父进程的页表;父进程和子进程需要关闭相应文件(子进程:close(_listenSock); 父进程: close(serviceSock);)
  • 子进程退出会产生僵尸问题(我们需要父进程不阻塞式处理)
    • a. 信号捕捉;主动忽略(signal(SIGCHLD, SIG_IGN);)
    • b. 子进程再fork() ,子进程退出;子进程的子进程执行服务(它退出后形成孤儿进程被Init 1一号进程领养)

我们采用信号捕捉的方法

    void start(){// 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态signal(SIGCHLD, SIG_IGN);for (;;){// 4. 获取连接struct sockaddr_in src;memset(&src, 0, sizeof src);socklen_t len = sizeof src;//  获取新连接int serviceSock = accept(_listenSock, (struct sockaddr *)&src, &len);if (serviceSock < 0){LogMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));continue;}// 获取连接成功了uint16_t clientPort = ntohs(src.sin_port);string clientIp = inet_ntoa(src.sin_addr);LogMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",serviceSock, clientIp.c_str(), clientPort);// 开始进行通信服务啦// version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个// service(serviceSock, clientIp, clientPort);// version 2.0 -- 多进程版 --- 创建子进程// 让子进程给新的连接提供服务,子进程能打开父进程曾经打开的文件fd 1 0pid_t id = fork();assert(id != -1);if (id == 0){//  子进程:提供服务    不需要监听socketclose(_listenSock);service(serviceSock, clientIp, clientPort);exit(0); //  子进程退出会产生僵尸问题;需要主动忽略信号来解决}close(serviceSock);}}
  • telnet简单测试服务器
    在这里插入图片描述

6. 服务器运行(多线程版)

class ThreadData
{
public:int _sock;std::string _ip;uint16_t _port;
};

在这里插入图片描述

  • 多线程代码如上所示
  • 在多线程这里不用进程关闭特定的文件描述符;因为Linux下的同一个进程中线程共用一张页表

3-3 客户端代码

  • TCP和UDP服务器代码都不需要显示绑定bind
  • tcp需要链接服务器( connect())
 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);1)sockfd:标识一个套接字。
(2)serv_addr:套接字s想要连接的主机地址和端口号。
(3)addrlen:name缓冲区的长度。
(4)如果链接或者绑定成功则返回 0,失败返回-1,错误原因存于errno 中。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
1)sockfd是socket()的返回值,文件描述符
(2)buf是接受数据的缓存区的指针
(3)len是发送数据的长度
(4)flags标志位,默认为0。
(5)返回值:成功则返回接收到的字符数,失败返回-1.
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
1)sockfd是socket()的返回值,文件描述符
(2)buf是接受数据的缓存区的指针
(3)len是发送数据的长度
(4)flags标志位,默认为0。
(5)返回值:成功则返回接收到的字符数,失败返回-1.
  • (长链接)代码块:
#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cassert>
#include "Log.hpp"using std::cout;
using std::endl;
using std::string;static const size_t SIZE = 1024;
static void Usage(string proc)
{std::cout << "\nUsage: " << proc << " clientIP port\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]); // 使用手册exit(1);}string ipClient = argv[1];uint16_t portClient = atoi(argv[2]);//  创建字节套接字int sockClient = socket(AF_INET, SOCK_STREAM, 0);if (sockClient < 0) //  创建失败{std::cerr << "client: 创建套接字失败 " << endl;exit(2);}//  不需要显示bind//  但需要建立链接struct sockaddr_in server;memset(&server, 0, sizeof server);server.sin_family = AF_INET;server.sin_port = htons(portClient);server.sin_addr.s_addr = inet_addr(ipClient.c_str());if (connect(sockClient, (struct sockaddr *)&server, sizeof server) < 0){std::cerr << "client: 建立链接失败 " << endl;exit(3);}//  建立链接成功string line;while (true){//  通信cout << "请输入# ";std::getline(std::cin, line);if (line == "quit")break;send(sockClient, line.c_str(), line.size(), 0); //  write(向sockClient中写数据);  发送数据char buffer[SIZE];ssize_t s = recv(sockClient, buffer, sizeof(buffer) - 1, 0); //  read;   接受数据if (s > 0){buffer[s] = 0;cout << "server 回显# " << buffer << endl;}else if (s == 0) // 对写端关闭连接{cout << "shutdown, me too!" << endl;break;}else{std::cerr << "recv socket error" << endl;break;}}return 0;
}
  • 短连接代码块:
#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cassert>
#include "Log.hpp"using std::cout;
using std::endl;
using std::string;static const size_t SIZE = 1024;
static void Usage(string proc)
{std::cout << "\nUsage: " << proc << " clientIP port\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]); // 使用手册exit(1);}string serverip = argv[1];uint16_t serverport = atoi(argv[2]);bool alive = false;int sock = -1;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呢?不需要显示的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 << "client: 创建套接字失败 " << endl;exit(2);}cout << "链接成功" << endl;alive = true;}//  链接上服务器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);buffer[s] = 0;cout << "server 回显# " << buffer << std::endl;}else if (s == 0){cout << "shutdown, me too!" << endl;alive = false;close(sock);}else{std::cerr << "recv socket error" << endl;alive = false;close(sock);}}return 0;
}

长链接和短链接在线程池里结果更明显

3-4 结果展示

  • 多进程版服务器群聊功能
    在这里插入图片描述
  • 多线程版服务器群聊功能
    在这里插入图片描述

3-5 总代码链接

链接:
https://gitee.com/ding-xushengyun/linux__cpp/tree/master/tcp

相关文章:

网络编程套接字

文章目录1. socket编程接口1-1 socket 常见API1-2 sockaddr结构2. 简单的UDP网络程序2-1 日志&#xff08;固定用法&#xff1a;标准部分自定义部分&#xff09;2-2 服务器代码实现1. 框架2. 初始化服务器3. 服务器运行4. 调用服务器封装函数&#xff08;UdpServer&#xff09;…...

海量数据相似数据查询方法

1、海量文本常见 海量文本场景&#xff0c;如何寻找一个doc的topn相似doc&#xff0c;一般存在2个问题&#xff0c; 1)、两两对比时间o(n^2) 2)、高维向量比较比较耗时。 文本集可以看成(doc,word)稀疏矩阵&#xff0c;一般常见的方法是构建到排索引&#xff0c;然后进行归并…...

Codeforces Round #822 (Div. 2)

A(签到) - Select Three Sticks 题意&#xff1a; 给你一个长度为 n 的正整数序列&#xff0c;你可以操作任意次&#xff0c;每一次操作可以选择任意一个元素&#xff0c;把它 1 或者 - 1&#xff0c;问最少多少次操作可以使得序列中存在三个相同的数字以构成一个等边三角形.…...

华为OD机试 - 最短木板长度(JS)

最短木板长度 题目 小明有 n n n块木板,第 i i i(1≤ i i </...

java设计模式——观察者模式

概述 定义:又被称为发布-订阅(Publish/Subscribe)模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时&#xff0c;会通知所有的观察者对象&#xff0c;使他们能够自动更新自己。 结构 在观察者模式…...

linux高级命令之线程的注意点

线程的注意点学习目标能够说出线程的注意点1. 线程的注意点介绍线程之间执行是无序的主线程会等待所有的子线程执行结束再结束线程之间共享全局变量线程之间共享全局变量数据出现错误问题2. 线程之间执行是无序的import threading import timedeftask():time.sleep(1)print(&qu…...

MyBatisPlus ---- 多数据源

MyBatisPlus ---- 多数据源1. 创建数据库及表2. 引入依赖3. 配置多数据源4. 创建用户service5. 创建商品service6. 测试适用于多种场景&#xff1a;纯粹多库、读写分离、一主多从、混合模式等 目前我们就来模拟一个纯粹多库的一个场景&#xff0c;其他场景类似 场景说明&#x…...

Java多线程

目录1 多线程1.1 进程1.2 线程1.3 多线程的实现方式1.3.1 方式1&#xff1a;继承Tread类1.3.2 方式2&#xff1a;实现Runnable接口1.3.3 方式3&#xff1a;实现Callable接口1.4 设置和获取线程名称1.5 线程调度1.6 线程控制1.7 线程生命周期1.8 数据安全问题之案例&#xff1a;…...

linux高级命令之线程执行带有参数的任务

线程执行带有参数的任务学习目标能够写出线程执行带有参数的任务1. 线程执行带有参数的任务的介绍前面我们使用线程执行的任务是没有参数的&#xff0c;假如我们使用线程执行的任务带有参数&#xff0c;如何给函数传参呢?Thread类执行任务并给任务传参数有两种方式:args 表示以…...

管理会计报告和财务报告的区别

财务会计报告是给投资人看的&#xff0c;可以反映公司总体的盈利能力。不过&#xff0c;我们回顾一下前面“第一天”里面提到的问题。如果你是公司的产品经理&#xff0c;目前有三个产品在你的管辖范围内。上级给你一笔新的资金&#xff0c;这笔资金应该投到哪个产品上&#xf…...

华为OD机试 - 最左侧冗余覆盖子串(Python) | 机试题算法思路 【2023】

最近更新的博客 华为OD机试 - 自动曝光(Python) | 机试题算法思路 【2023】 华为OD机试 - 双十一(Python) | 机试题算法思路 【2023】 华为OD机试 - 删除最少字符(Python) | 机试题算法思路 【2023-02】 华为OD机试 - Excel 单元格数值统计(Python) | 机试题算法思路 …...

【Opencv 系列】第1章 图像基础

通过本套课程,可以学到: 1.opencv的基本操作 2.两个案例,目标追踪&人脸识别 对重点内容,我会提示,包括我再准备这套课程过程中遇到的坑点! 最后代码我会放到git上,章节顺序一致:https://github.com/justinge/opencv_tutorial.git 系列文章目录 第1章 Opencv 图像基础 和 …...

创建和销毁对象——遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器有个共同的局限性&#xff1a;它们都不能很好地扩展到大量的可选参数。比如用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的&#xff1a;每份的含量、每罐的含量以及每份的卡路里。还有超过20个的可选域&#xff1a;总脂肪量、饱和脂…...

【c++学习】入门c++(中)

目录一. 前言二. 函数重载1. 概念2.函数名修饰规则三 .引用&#xff08;&&#xff09;1. 概念2. 引用特性3.应用1.做参数2. 做返回值3. 传值、传引用效率比较4.引用和指针的区别四 . 结语一. 前言 小伙伴们大家好&#xff0c;今天我们继续学习c入门知识&#xff0c;今天的…...

论文阅读_AlphaGo_Zero

论文信息 name_en: Mastering the game of Go without human knowledge name_ch: 在没有人类知识的情况下掌握围棋游戏 paper_addr: http://www.nature.com/articles/nature24270 doi: 10.1038/nature24270 date_publish: 2017-10-01 tags: [‘深度学习’,‘强化学习’] if: 6…...

一文教你用Python创建自己的装饰器

python装饰器在平常的python编程中用到的还是很多的&#xff0c;在本篇文章中我们先来介绍一下python中最常使用的staticmethod装饰器的使用。 目录一、staticmethod二、自定义装饰器python类实现装饰器python函数嵌套实现装饰器多个装饰器调用三、带参数的装饰器一、staticmet…...

华为OD机试 - 任务总执行时长(JS)

任务总执行时长 题目 任务编排服务负责对任务进行组合调度。参与编排的任务又两种类型,其中一种执行时长为taskA,另一种执行时长为taskB。任务一旦开始执行不能被打断,且任务可连续执行。服务每次可以编排num个任务。 请编写一个方法,生成每次编排后的任务所有可能的总执…...

pytorch离线快速安装

1.pytorch官网查看cuda版本对应的torch和torchvisionde 版本(ncvv -V&#xff0c;nvidia-sim查看cuda对应的版本) 2.离线下载对应版本&#xff0c;网址https://download.pytorch.org/whl/torch_stable.html 我下载的&#xff1a; cu113/torch-1.12.0%2Bcu113-cp37-cp37m-win_…...

华为OD机试 - 数组合并(JS)

数组合并 题目 现在有多组整数数组,需要将他们合并成一个新的数组。 合并规则,从每个数组里按顺序取出固定长度的内容合并到新的数组中, 取完的内容会删除掉, 如果该行不足固定长度或者已经为空, 则直接取出剩余部分的内容放到新的数组中,继续下一行。 如样例1,获得长度3,先遍…...

不要让GPT成为你通向“学业作弊”的捷径——使用GPT检测工具来帮助你保持正确的方向

不要让GPT成为你通向“学业作弊”的捷径——使用GPT检测工具来帮助你保持正确的方向 最近&#xff0c;多所美国高校以及香港大学等都明确禁止在校使用ChatGPT等智能文本生成工具。GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种自然语言处理技术&#x…...

基于matlab的斜视模式下SAR建模

一、前言此示例说明如何使用线性 FM &#xff08;LFM&#xff09; 波形对基于聚光灯的合成孔径雷达 &#xff08;SAR&#xff09; 系统进行建模。在斜视模式下&#xff0c;SAR平台根据需要从宽侧斜视一定角度向前或向后看。斜视模式有助于对位于当前雷达平台位置前面的区域进行…...

15-基础加强-1-类加载器反射

文章目录1.类加载器1.1类加载器【理解】1.2类加载的过程【理解】1.3类加载的分类【理解】1.4双亲委派模型【理解】1.5ClassLoader 中的两个方法【应用】2.反射2.1反射的概述【理解】2.2获取Class类对象的三种方式【应用】 第1步&#xff1a;获取类的Class对象2.3反射获取构造方…...

基于SSM,Spring, BootStrap 毕业设计管理系统的设计与实现

目录 一.前言介绍 二、主要技术 2.1 SSM框架介绍 2.2 MYSQL数据库 2.3 持久层框架MyBatis 2.4 前端框架BootStrap 三. 系统设计 3.1 系统架构设计 3.2 系统功能模块 3.2.1 学生模块 3.2.2 教师模块 3.2.3 管理员模块 四、数据库设计 4.1 数据分析 4.2 概念设计 …...

一招鉴别真假ChatGPT,并简要介绍ChatGPT、GPT、GPT2和GPT3模型之间的区别和联系

以下内容除红色字体部分之外&#xff0c;其他均来源于ChatGPT自动撰写。 ChatGPT是基于GPT模型的对话生成模型&#xff0c;旨在通过对话模拟实现自然语言交互。它是为了改善人机对话体验而设计的&#xff0c;主要应用于聊天机器人、智能客服等场景。 与GPT模型相比&#xff0c;…...

华为OD机试 - 特异性双端队列(JS)

特异性双端队列 题目 有一个特异性的双端队列,该队列可以从头部到尾部添加数据,但是只能从头部移除数据。 小A一次执行 2n 个指令往队列中添加数据和移除数据, 其中 n 个指令是添加数据(可能从头部也可以从尾部添加) 依次添加 1 到 n , n 个指令是移出数据 现在要求移除数…...

Nginx自动封禁可疑Ip

文章目录一、Nginx封禁ip1、简介2、nignx 禁止IP访问2.1 方法一2.2 方法二3、关于 deny 的使用二、脚本自动封禁Ip1、流程介绍2、脚本实战2.1 核心脚本解释2.2 编写shell脚本2.3 crontab定时一、Nginx封禁ip 1、简介 在网站维护过程中&#xff0c;有时候我们需要对一些IP地址…...

分布式事务--理论基础

1、事务基础 1.1、什么是事务 事务可以看做是一次大的活动&#xff0c;它由不同的小活动组成&#xff0c;这些活动要么全部成功&#xff0c;要么全部失败。 1.2、本地事务 在同一个进程内&#xff0c;控制同一数据源的事务&#xff0c;称为本地事务。例如数据库事务。 在计…...

Matlab数学建模常用算法及论文插图绘制模板资源合集

最近有很多朋友咨询我关于Matlab论文插图绘制方面的问题。 问了一下&#xff0c;这些朋友中&#xff0c;除了写博士论文的&#xff0c;大部分都是要参加美赛的。 这让我突然想起&#xff0c;自己曾经为了水论文&#xff0c;购买过一批Matlab数学建模的资料。 想了想&#xf…...

C语言【动态内存管理 后篇】

动态内存管理 后篇&#x1fac5;经典例题&#x1f926;‍♂️题目1&#x1f926;‍♂️题目2&#x1f926;‍♂️题目3&#x1f926;‍♂️题目4&#x1fac5;C/C程序的内存开辟前面的一篇文章动态内存管理 前篇&#xff0c;我们已经了解过了动态内存管理的相关信息&#xff0c…...

四大步骤,教你彻底关闭Win10自动更新

文章目录一、禁用Windows Update服务二、在组策略里关闭Win10自动更新相关服务三、禁用任务计划里边的Win10自动更新四、在注册表中关闭Win10自动更新参考资料一、禁用Windows Update服务 1、同时按下键盘 Win R&#xff0c;打开运行对话框&#xff0c;然后输入命令 services…...