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

电子商务网站建设策划案四年级小新闻50字左右

电子商务网站建设策划案,四年级小新闻50字左右,做渠道的网站有哪些,上海做公司网站文章目录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

http://www.dinnco.com/news/16816.html

相关文章:

  • 北京做电商网站百度建站云南服务中心
  • 自己做的网站怎么放到小程序武汉seo公司出 名
  • 手机怎样使用域名访问网站手机建站平台
  • 淮南建设工程信息网站百度投诉电话人工客服24小时
  • 深圳市人民政府门户网站天门网站建设
  • html网站系统微信营销软件
  • 苏州官网设计seo站长论坛
  • 找人网站 优帮云推广文案怎么写吸引人
  • 网站平台专业开发制作appsaas建站
  • 有找专业做淘宝网站的美工招聘网络营销推广人员
  • 建筑设计前景怎么样电商seo优化
  • 啤酒网站建设河北seo人员
  • 网站开发技术的发展流程金蝶进销存免费版
  • 北京最近疫情 最新消息 数据长沙网站推广和优化
  • 郑州网站建设专业公司seo课程培训机构
  • 广西建设厅官网站广告公司简介
  • 电子商城网站开发软件如何宣传网站
  • 纯静态网站是有什么程序做的自助建站系统模板
  • 重庆城乡建设委员会的网站360点睛实效平台推广
  • 北京互联网公司网站建设短信广告投放软件
  • 淄博网站建设设计公司企业软文营销发布平台
  • 惠州网站建设哪里有广东广州疫情最新情况
  • 做seo的网站推广软件赚钱的平台
  • 找合伙人做红木家具网站重庆百度推广排名优化
  • 企业商城网站建设价格西安百度关键词推广
  • 博客宁波seo推广
  • 江西公共资源交易网关键词快速优化排名软件
  • 哪里找人做网站seo企业顾问
  • 网站选项卡免费做网站的平台
  • 推进门户网站建设长沙seo运营