【计算机网络】socket 网络套接字

这篇具有很好参考价值的文章主要介绍了【计算机网络】socket 网络套接字。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、端口号

1. 认识端口号

实际上我们两台机器在进行通信时,是应用层在进行通信,应用层必定会推动下层和对方的上层进行通信。

其实网络协议栈中的下三层,主要解决的是数据安全可靠的送到远端机器。而用户使用应用层软件,完成数据发送和接收的。那么用户要使用软件,首先需要把这个软件启动起来!所以软件启动起来,本质就是进程!所以两台机器进行通信,本质是两台机器之上的应用层在通信,也就是两个进程之间在互相交换数据!所以网络通信的本质就是进程间通信!只不过在网络通信中的公共资源是网络,通过网络协议栈利用网络资源,让两个不同的进程看到了同一份资源!

在网络协议栈中,在传输层怎么把数据正确交给上层应用层呢?怎么知道交给哪一个应用呢?所以就要求上层应用层和传输层之间必须协商一种方案,让我们把数据准确交给上层,这个方案我们称为端口号。所以在传输层的报头中,必须要有原端口号目的端口号,也就是根据目的端口号就可以决定这个数据的有效载荷要交给上层应用的哪一个!所以对于端口号无论对于客户端和服务端,都能唯一的标识该主机上的一个网络应用层的进程!

我们可以这样理解,其实在传输层当中,操作系统会形成一张哈希表,哈希表中的类型是 task_struct*,每一个应用层都要和该哈希表绑定端口号,本质就是根据端口号在哈希表里做哈希运算,如果该位置已经被占用了,就不能被绑定了,因为一个端口号只能被一个进程绑定;如果该位置没有被使用,就把该进程的pcb地址放在该位置上。

2. socket

因为在公网上,IP地址 能表示唯一的一台主机,端口号 port,用来标识该主机上的唯一的一个进程,所以 IP + port 就可以标识全网唯一的一个进程!那么我们在网络通信时,只需要在对应的报头上填上原IP目的IP原port目的port,就可以将报文交给另一个主机的进程,这种基于 IP + port 的通信方式,我们称为 socket.

那么端口号和进程pid有什么区别呢?进程pid也能标识一台主机上的唯一进程啊?因为首先,不是所有的进程都要通信,但是所有的进程都要有pid!其次是为了使系统和网络功能解耦!

二、认识TCP协议和UDP协议

下面我们先认识一下两个传输层协议:

1. TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;后面我们再详细讨论 TCP 的一些细节问题。

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

2. UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;后面再详细讨论。

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

三、网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端;否则就忽略,直接发送即可;

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

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

  • 这些函数名很好记,h 表示 hostn 表示 networkl 表示 32 位长整数,s 表示16位短整数;
  • 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送;
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

四、socket 编程

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);

2. sockaddr 结构

socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的 UNIX Domain Socket;然而,各种网络协议的地址格式并不相同

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

  • IPv4IPv6 的地址格式定义在 netinet/in.h 中,IPv4地址用 sockaddr_in 结构体表示,包括16位地址类型, 16位端口号和32位IP地址.;
  • IPv4IPv6 地址类型分别定义为常数 AF_INETAF_INET6,这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容;
  • socket API 可以都用 struct sockaddr* 类型表示,在使用的时候需要强制转化成 sockaddr_in;这样的好处是程序的通用性,可以接收IPv4IPv6,以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数。

3. 编写 UDP 服务器

(1)socket()

下面我们编写一个 UDP 服务器。首先需要做的是创建套接字,使用到的接口是 socket()

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

第一个参数是我们创建的套接字的域,即使用 IPv4 的网络协议还是 IPv6 的网络协议,目前我们只需要关注这两个即可,如下图:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

第二个参数表示当前 socket 对应的类型,也就是相当于这个套接字未来给我们提供什么服务,是面向字节流的还是面向用户数据报的,如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

第三个参数表示的是协议类型,目前我们不需要传这个参数。

而返回值相当于是一个文件描述符,所以创建一个套接字的本质,在底层就相当于是打开一个文件,只不过以前的 struct file 指向的是键盘、显示器这样的设备;而现在指向的是网卡设备。

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

(2)bind()

创建套接字成功之后,接下来就要绑定端口号,使用到的接口是 bind(),如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

其中第一个参数就是创建套接字时的返回值;第二个参数是一个结构体;第三个参数是结构体的长度。但是我们在网络套接字编程的时候不用第二个参数类型的结构体,这个结构体它只是设计接口用,我们实际用的是 sockaddr_in 类型的结构体,只需要在传参的时候进行强转即可。我们可以使用 bzero() 接口将该结构体清0;

我们是要使用 bind 来让套接字和我们往该结构体中填充的网络信息要关联起来,所以我们需要想该结构体中填充对应的字段。该结构体中有如下字段:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

对应下图:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

其中 sin_zero 为该结构体的填充字段,也就是这些字段不用填充,当作占位符即可;sin_addr 代表 ip 地址;sin_port 代表服务器所使用的端口号;sin_family 代表该结构体对应的网络协议类型,IPv4 或者 IPv6.

因为我们在给对方发送数据的时候,我们也一定需要让对方知道我们是谁,所以我们需要将端口号携带上,发送给对方,这样对方把数据处理完,就可以给我们响应回来。所以端口号是要在网络里来回发送的,也就是需要保证我们的端口号是网路字节序列,因为该端口号是要给对方发送的。所以这里我们就需要用到主机序列转网络序列的接口,由于端口号是两个字节,所以用到的接口为 htons()

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

由于我们用户一般用的都是点分十进制字符串风格的 IP 地址,也就是 0.0.0.0 这种风格,每个点分的范围是 0~255,每个字符一个字节,远远超过结构体中要求的 32 位 ip 地址,也就是四字节。所以我们需要将该字符串类型转换为 uint32_t 的类型,那么用到的接口是 inet_addr(),它的作用就是将字符串风格的 ip 地址转化为网络风格的 uint32_t 类型,如下图:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

同端口号一样,IP 地址也需要保证是网络字节序列。那么它的返回值类型 in_addr_t 其实就是符合网路字节序列的 uint32_t 的类型。

上面我们已经把准备工作做好了,接下来我们就需要使用 bind() 接口进行绑定,本质就是把我们定义的 struct 结构体设置进内核,设置进指定的套接字内部。

(3)recvfrom()

接下来我们就需要在指定的一个套接字里获取数据内容,使用到的接口是 recvfrom(),如下图:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

第一个参数就是网络文件描述符;第二个参数和第三个参数分别表示我们提供的缓冲区和它的长度,读到的数据就会放在缓冲区中;第三个参数设为0就是默认使用阻塞方式;最后两个参数又是熟悉的结构体,由于我们需要知道这些数据是谁给我们发的,因为我们有可能也要将数据给对方返回。所以最后两个参数其实是输出型参数。

返回值成功就是对应的长度,否则就是-1,如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

(4)sendto()

将数据发送回给对方使用到的接口为 sendto(),如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

参数和 recvfrom() 的参数类似,这里不再介绍了。而最后两个参数是输入型参数,我们要将数据发回给对方,首先需要知道对方是谁,而我们上面已经通过 recvfrom() 获取到了对方的结构体信息,所以直接使用该结构体信息即可。

(5)udp 服务端和客户端

其中通过使用上面的接口编写的一个简单的接收客户端的字符串信息,并进行简单的加工的 udp 服务器代码链接为:UDP.

其中 udp server 的代码如下:

				#pragma once
				
				#include <iostream>
				#include <string>
				#include <sys/types.h>
				#include <sys/socket.h>
				#include <netinet/in.h>
				#include <arpa/inet.h>
				#include <strings.h>
				#include <unistd.h>
				#include <cstring>
				#include <functional>
				
				#include "log.hpp"
				
				using func_t = std::function<std::string(const std::string&)>;
				//typedef std::function<std::string(const std::string&)> func_t;
				
				std::string default_ip = "0.0.0.0";
				uint16_t default_port = 8080;
				
				log lg;
				
				class UdpServer
				{
				public:
				    UdpServer(const uint16_t &port = default_port, const std::string &ip = default_ip)
				        : _port(port), _ip(ip), _isrunning(false), _sockfd(0)
				    {}
				
				    void Init()
				    {
				        // 1.创建 udp 套接字
				        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET == PF_INET
				        if (_sockfd < 0)
				        {
				            lg(Fatal, "socket create faild, sockfd: %d", _sockfd);
				            exit(1);
				        }
				        lg(Info, "socket create success, sockfd: %d", _sockfd);
				
				        // 2.绑定端口号
				        // 2.1 准备数据
				        struct sockaddr_in local;
				        bzero(&local, sizeof(local));
				        local.sin_family = AF_INET;
				        local.sin_port = htons(_port); // 主机序列转网络序列
				        // local.sin_addr.s_addr = inet_addr(_ip.c_str());   // 1.string -> uint32_t  2.保证uint32_t是网络序列
				        local.sin_addr.s_addr = htonl(INADDR_ANY);
				
				        // 2.2 开始bind
				        int n = bind(_sockfd, (const sockaddr *)&local, sizeof(local));
				        if (n < 0)
				        {
				            lg(Fatal, "bind faild, errno: %d, err message: %s", errno, strerror(errno));
				            exit(2);
				        }
				        lg(Info, "bind success, errno: %d, err message: %s", errno, strerror(errno));
				    }
				
				    void Run(func_t func)
				    {
				        _isrunning = true;
				        char buffer[1024];
				        while (_isrunning)
				        {
				            // 记录客户端发来时的结构体信息
				            struct sockaddr_in client;
				            socklen_t len = sizeof(client);
				            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&client, &len);
				            if (n < 0)
				            {
				                lg(Warning, "recvfrom error, errno: %d, err message: %s", errno, strerror(errno));
				                continue;
				            }
				
				            buffer[n] = 0;
				            // 对数据进行简单的加工
				            std::string info = buffer;
				            std::string echo_string = func(info);
				
				            // 发送回给对方
				            sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const sockaddr *)&client, len);
				        }
				    }
				
				    ~UdpServer()
				    {
				        if (_sockfd > 0)
				            close(_sockfd);
				    }
				
				private:
				    int _sockfd;
				    uint16_t _port;
				    std::string _ip;
				    bool _isrunning;
				};

udp client 的代码如下:

				#include <iostream>
				#include <cstdlib>
				#include <unistd.h>
				#include <strings.h>
				#include <sys/types.h>
				#include <sys/socket.h>
				#include <netinet/in.h>
				#include <arpa/inet.h>
				
				using namespace std;
				
				void Usage(string proc)
				{
				    cout << "\n\rUsage: " << proc << " serverip serverport\n" << endl;
				}
				
				int main(int argc, char* argv[])
				{
				    if(argc != 3)
				    {
				        Usage(argv[0]);
				        exit(0);
				    }
				    string server_ip = argv[1];
				    uint16_t server_port = stoi(argv[2]);
				
				    sockaddr_in server;
				    bzero(&server, sizeof(server));
				    server.sin_family = AF_INET;
				    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
				    server.sin_port = htons(server_port);
				    socklen_t len = sizeof(server);
				
				    // client 也需要 bind,只不过不需要用户显示 bind,一般由OS自由随机选择
				    // 系统会在首次发送数据的时候给我们bind
				    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
				    if(sockfd < 0)
				    {
				        cout << "socker error" << endl;
				        return 1;
				    }
				
				    string message;
				    char buffer[1024];
				
				    while(true)
				    {
				        cout << "Plase Enter@ ";
				        getline(cin, message);
				
				        // 发送数据
				        sendto(sockfd, message.c_str(), message.size(), 0, (sockaddr*)&server, len);
				
				        // 当服务器进行简单的加工处理后会发送回来,此时客户端再次获取
				        sockaddr_in temp;
				        socklen_t size = sizeof(temp);
				
				        ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (sockaddr*)&temp, &len);
				        if(s > 0)
				        {
				            buffer[s] = 0;
				            cout << buffer << endl;
				        }
				    }
				    close(sockfd);
				    return 0;
				}

main 函数:

				#include <iostream>
				#include <vector>
				#include <memory>
				#include <cstdio>
				
				#include "UdpServer.hpp"
				
				using namespace std;
				
				void Usage(string proc)
				{
				    cout << "\n\rUsage: " << proc << " port[1024+]\n" << endl;
				}
				
				// 处理字符串的方法
				string Handler(const std::string& str)
				{
				    string res = "Server get a message: ";
				    res += str;
				    cout << res << endl;
				
				    return res;
				}
				
				// 远程执行指令的方法
				string ExcuteCommand(const string& cmd)
				{
				    FILE* fp = popen(cmd.c_str(), "r");
				    if(nullptr == fp)
				    {
				        perror("popen");
				        return "error";
				    }
				
				    string result;
				    char buffer[4096];
				    while(true)
				    {
				        char* tmp = fgets(buffer, sizeof(buffer), fp);
				        if(tmp == nullptr) break;
				        result = buffer;
				    }
				    pclose(fp);
				    return result;
				}
				
				int main(int argc, char* argv[])
				{
				    if(argc != 2)
				    {
				        Usage(argv[0]);
				        exit(0);
				    }
				
				    uint16_t port = stoi(argv[1]);
				    unique_ptr<UdpServer> svr(new UdpServer(port, "127.0.0.1"));
				
				    svr->Init();
				    svr->Run(ExcuteCommand);
				
				    return 0;
				}

有关代码中的细节:

  • 有关 IP 地址

云服务器禁止直接bind公网ipbind ip 地址为0,表示的含义是任意地址绑定,这种是比较推荐的做法。当 IP 地址为 127.0.0.1 时,表示进行的是本地传输测试,不会进行跨网传输。

  • 有关 port

其中 0~1023 的端口号是系统内定的端口号,一般都要有固定的应用层协议使用,例如 http:80,https:443;所以我们一般绑端口号,一般绑1024以上的。

  • popen() 系统调用

popen() 是一个被封装起来的管道和子进程执行命令的应用。

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

它的第一个参数就是需要执行的命令,在底层它会帮我们进行 fork() 创建子进程,并让父子进程建立管道,然后让子进程把它的运行结果通过管道再返回给调用方。如果调用方想得到 command 指令的运行结果,可以通过文件指针的方式读取。第二个参数相当于是打开这个命令的方式,我们使用 “r” 即可。使用完毕后使用 pclose() 关闭该文件指针即可。

其中,我们可以使用 netstat -nlup 查看系统中所有的 udp 信息,并且把进程信息也显示出来。

我们还可以将以上代码修改成为多线程代码,链接为:多线程UDP.

4. 地址转换函数

(1)相关接口

我们只介绍基于 IPv4socket 网络编程,sockaddr_in 中的成员 struct in_addr sin_addr 表示32位 的 IP 地址,但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示和 in_addr 表示之间转换。我们在上面的 bind() 中也使用了地址转换函数 inet_addr().

  • 字符串转 in_addr 的函数:

      			#include <arpa/inet.h>
      			
      			int inet_aton(const char* strptr, struct in_addr* addrptr);
      			in_addr_t inet_addr(const char* strptr);
      			int inet_pton(int family, const char* strptr, void* addrptr);
    
  • in_addr 转字符串的函数:

      			char* inet_ntoa(struct in_addr inaddr);
      			const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);
    

其中 inet_ptoninet_ntop 不仅可以转换 IPv4in_addr,还可以转换 IPv6in6_addr,因此函数接口是 void* addrptr.

(2)关于 inet_ntoa

inet_ntoa 这个函数返回了一个 char*,很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果,那么是否需要调用者手动释放呢?

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

man 手册上说,inet_ntoa 函数,是把这个返回结果放到了静态存储区。这个时候不需要我们手动进行释放。

5. 编写 TCP 服务器

(1)listen()

TCP 是面向连接的,服务器一般是比较被动的,所以服务器一直处于一种等待连接到来的状态,这个工作叫做监听状态,使用到的接口是 listen(),如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

第一个参数为指定的套接字,通过该套接字等待新连接的到来。第二个参数我们后面再介绍,暂时设为10左右即可。返回值,成功返回0,失败返回-1.

(2)accept()

因为 TCP 是面向连接的,所以在正式通信之前,先要把连接建立起来,使用到的接口为 accept(),该接口的作用是获取一个新的连接,如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

第一个参数为我们刚刚设置为监听状态的套接字;后两个参数和 recvfrom() 的后两个参数一样,都是输出型参数,也就是谁给我们发的 TCP 报文,那么对应的套接字信息就会通过这两个参数返回出来。

而返回值成功返回一个文件描述符;否则返回-1;那么返回值也是一个文件描述符,我们原本也有一个文件描述符,为什么会有两个 sockfd 呢?我们该用哪个呢?其实它们分工是明确的,我们原本定义的 sockfd,即被创建的,被 bind 的,被监听的套接字,它的工作是从底层获取新的连接;而未来真正提供通信服务的,是 accept() 返回的套接字!

至此,我们可以使用 telnet 进行指定服务的一个远程连接,后面跟上 IP 地址和端口号即可;它在底层默认使用的就是 TCP.

(3)con

由于在 TCP 中,客户端是要连接服务器的,所以服务端需要有一个能够向服务器发起连接的接口,该接口为 connect(),如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

该接口的作用是通过指定的套接字,向指定的网络目标地址发起连接。后两个参数和 sendto() 的后两个参数一样。返回值成功返回0,失败返回-1.

TCP 客户端也需要 bind,但是和 UDP 一样,不需要显示的 bind,系统会在客户端发起 connect 的时候,进行自动随机 bind.

我们可以使用 netstat -nltp 查看系统中所有 TCP 的信息,并把进程信息显示出来。

(4)守护进程

在我们登录 Linux 的时候,Linux 系统会给我们形成一个会话,而且会为每个会话创建一个 bash 进程,这个 bash 就可以为用户提供命令行服务。每个会话中只能存在一个前台进程,但是可以存在多个后台进程,而键盘信号只能发送给前台进程。前台和后台进程的区别就是是否拥有键盘文件,它们都可以向显示器打印,而只有前台进程才能从键盘,即标准输入获取数据!

如果我们不想后台进程向显示器打印的数据影响我们,我们可以将它的打印数据重定向到文件中,例如:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

其中 [1] 表示后台任务号,后面数字表示进程 PID.

而查看后台任务的指令为:jobs,如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

如果我们想把后台进程提到前台,可以使用 fg 任务号,如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

如果想把它重新放回后台,我们可以使用 ctrl + z 将该进程暂停。然后使用 bg 任务号 将该进程重新启动,如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

接下来我们再运行几个后台进程,例如使用 sleep,方便观察 Linux 中的进程间关系,使用 ps axj | head -1 && ps axj | grep -Ei 'a.out|sleep' 查看它们的进程信息:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

其中 PPID、PID 我们都认识,而 PGID 表示的是进程组IDSID 表示 session id,即会话 id.

而系统中可能会存在多个 session,所以系统需要管理多个 session.

我们可以看到,./a.out 进程的 PIDPGID 是一样的,所以它就是自成进程组的。而三个 sleep 分别是三个不同的进程,但是它们的 PGID 却是同一个,而且是用管道建立的进程的第一个进程的 PID,所以它们三个自成一组,而组长是多个进程中的第一个。那么进程组和任务有什么关系呢?任务是要指派给进程组的!所以我们需要校正一下以前的说法,我们把前台进程称为前台任务,后台进程称为后台任务,因为可能某一个后台任务里面,可能会包含多个进程。但是无论有几个进程组完成对应的任务,在同一个会话内启动的,SID 是一样的!那么上面中的 SID 到底是谁呢?我们可以查看一下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

如上图,我们可以看到,它是 bash!所以就是以 bashpid 去构建了一个 session

这种后台进程会收到用户登录和退出的影响,如果我们不想受到任何用户登录和注销的影响,我们可以将进程守护进程化。什么是守护进程呢?我们把自成进程组自成会话的进程称为守护进程!那么我们该如何做到呢?下面我们认识一个接口:setsid(),如下:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

该接口的作用就是,哪个进程调用该接口,就把该进程的组ID设置为会话ID,也就是让进程独立成会话。

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器

返回值成功返回进程的ID,否则返回-1.

注意,该接口不能由进程组的组长直接调用,那么怎么才能保证不是组长调用呢?所以我们可以使用 fork() 创建子进程调用!所以守护进程的本质,也是孤儿进程!

(5)tcp 服务端和客户端

接下来我们结合上面所学的知识,编写一个 TCP 服务器,并将它守护进程化,代码链接:

其中在守护进程中,我们的代码中是充满大量的打印的,而这些打印默认是向标准输出打的,也就是向显示器上打了,而对于守护进程来说,就不应该向显示器上打了,所以我们需要一个解决方案。而 Linux 中存在一种字符文件,叫做 /dev/null,只要我们向该文件写入,都会被该文件丢弃掉,如果我们向该文件读取,什么也读取不到。所以我们只需要将所有的输出向该文件写入即可。我们也可以将打印信息写入文件中。

另外,TCP 在通信时是全双工的,也就是可以同时读写的。在底层操作系统给 TCP 提供两个缓冲区,一个发送缓冲区,一个接收缓冲区,我们在用 TCP 的同时,别人也在用,所以别人也会有上面两个缓冲区,所以当我们发送数据,是先把我们的数据拷贝到我们的 TCP 的发送缓冲区,然后通过网络会发送到对方的接收缓冲区,反过来也同理,如下图:

【计算机网络】socket 网络套接字,计算机网络,计算机网络,网络,linux,运维,c++,服务器文章来源地址https://www.toymoban.com/news/detail-837065.html

到了这里,关于【计算机网络】socket 网络套接字的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 计算机网络(2) --- 网络套接字UDP

    计算机网络(1) --- 网络介绍_哈里沃克的博客-CSDN博客 https://blog.csdn.net/m0_63488627/article/details/131967378?spm=1001.2014.3001.5501 目录 1.端口号 2.TCP与UDP协议 1.TCP协议介绍 1.TCP协议 2.UDP协议 3.理解 2.网络字节序 发送逻辑 3.socket 1.介绍 2.sockaddr结构 4.UDP协议编程 1.接口介绍 1.创建套接字

    2024年02月14日
    浏览(49)
  • 计算机网络(3) --- 网络套接字TCP

    计算机网络(2) --- 网络套接字UDP_哈里沃克的博客-CSDN博客 https://blog.csdn.net/m0_63488627/article/details/131977544?spm=1001.2014.3001.5501 目录 1.TCP 1.服务端接口介绍 1.listen状态 2.accept获取链接 2.客户端接口介绍 2.TCP的服务器和客户端接口实现 1.服务端 1.成员函数 2.接口 start()实现方式 1.单

    2024年02月14日
    浏览(53)
  • 【计算机网络】网络编程套接字(一)

    目录 1.预备知识 1.1.理解源IP地址和目的IP地址 1.2.认识端口号 1.2.1.理解\\\"端口号\\\"和\\\"进程ID\\\" 1.2.2.理解源端口号和目的端口号 1.3.认识TCP/UDP协议 1.3.1.TCP协议 1.3.2.UDP协议 1.4.网络字节序 网络字节序和主机字节序的转换 2.socket编程接口 2.1.sockaddr结构 struct sockaddr_in 的具体结构: 2.

    2024年02月08日
    浏览(53)
  • 【计算机网络】网络编程套接字&UDP服务器客户端的简单模拟

    需要云服务器等云产品来学习Linux的同学可以移步/–腾讯云–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦) 每台主机都有自己的IP地址,当数据在进行通信的时候,除了要发送的数据外,在报头里面还要包含发送方的IP和接收方的IP,这里发送方的IP就

    2024年02月20日
    浏览(64)
  • 计算机网络套接字编程实验-TCP单进程循环服务器程序与单进程客户端程序(简单回声)

    1.实验系列 ·Linux NAP-Linux网络应用编程系列 2.实验目的 ·理解并掌握在程序运行时从命令行读取数据的C语言编程方法; ·理解并掌握基于命令参数设置并获取IP与Port的C语言编程方法; ·理解并掌握套接字地址的数据结构定义与地址转换函数应用; ·理解并掌握网络字节序

    2024年02月11日
    浏览(97)
  • 计算机网络套接字编程实验-TCP多进程并发服务器程序与单进程客户端程序(简单回声)

    1.实验系列 ·Linux NAP-Linux网络应用编程系列 2.实验目的 ·理解多进程(Multiprocess)相关基本概念,理解父子进程之间的关系与差异,熟练掌握基于fork()的多进程编程模式; ·理解僵尸进程产生原理,能基于|sigaction()或signal(),使用waitpid()规避僵尸进程产生; ·

    2024年02月12日
    浏览(47)
  • 【Python】Python 网络编程 ( Socket 套接字简介 | Socket 套接字使用步骤 | Socket 套接字服务端与客户端开发 )

    Socket 套接字 是一种 进程之间的 通信机制 , 通过套接字可以在 不同的进程之间 进行数据交换 ; 在 网络编程 中 , Socket 套接字 主要用于 客户端 与 服务器 之间的 通信 , 大部分 网络相关的应用程序 , 都使用到了 Socket 套接字技术 ; 套接字有两种类型 : 流套接字 : 提供了一个可

    2024年02月15日
    浏览(153)
  • 【网络协议】聊聊套接字socket

    网络编程我们知道是通过socket进行编程的,其实socket也是基于TCP和UDP协议进行编程的。但是在socket层面是感知不到下层的,所以在设置参数的时候,其实是端到端协议智商的网络层和传输层。TCP是数据流所以设置为SOCK_STREAM,而UDP是基于数据报的,设置为SOCK_DGRAM 整体流程其实

    2024年02月07日
    浏览(42)
  • 网络编程套接字(Socket)

    认识IP地址, 端口号, 网络字节序等网络编程中的基本概念; 学习socket api的基本用法; 能够实现一个简单的udp客户端/服务器; 能够实现一个简单的tcp客户端/服务器(单连接版本, 多进程版本, 多线程版本); 理解tcp服务器建立连接, 发送数据, 断开连接的流程; 通俗易懂地说,源

    2024年01月21日
    浏览(58)
  • 【网络编程】socket套接字

    如果我们的台式机或者笔记本没有IP地址就无法上网,而因为每台主机都有IP地址,所以注定了数据从一台主机传输到另一台主机 一定有源IP和目的IP 。 所以在报头中就会包含源IP和目的IP。 而我们把数据从一台主机传递到另一台主机并不是目的,真正通信的其实是应用层上的

    2024年02月02日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包