Linux— 网络编程套接字

2023-10-30

目录

预备知识

认识端口号

理解源端口号和目的端口号

认识TCP协议

认识UDP协议

 网络字节序

 socket编程接口

socket 常见API

sockaddr结构

 sockaddr 结构​编辑

 sockaddr_in 结构

 in_addr结构

地址转换函数

简单的UDP网络程序

实现一个简单的英译汉的功能

简易的远程命令行操作 

 简易的多人对话

简单的TCP网络程序

 TCP socket API 详解

  socket():

  bind():

  listen():

  accept():

  connect 

简单的TCP 网络通话

 多进程版

 多线程版

TCP协议通讯流程

TCP 和 UDP 对比


预备知识

认识端口号

我们知道IP地址可以标识主机唯一性,但是每一台主机上的客户或服务进程的唯一性用什么来标识呢?

为了更好的标识一台主机上服务进程的唯一性,采用端口号port,标识服务器进程,客户端进程的唯一性。

端口号(port)是传输层协议的内容.
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用

 知道端口号之后有如下几个问题需要知道:

1. IP(标识全网主机唯一性) + 该主机上的端口号,标识服务器进程的唯一性。

  • 两个具有唯一性的进程进行网络通信时,本质上是在进行进程间通信,进程间通信就是让不同的进程看到同一份公共资源,那么这份公共资源就是网络。还有通信其实就是在做IO,所以我们的上网行为就分为两种:我要把数据发出去;我要接收别人的数据。

2. IP保证全网唯一,port保证在主机内部的唯一性。

3. 进程已经有了PID了,为什么还要有port呢?

  • 因为系统是系统,网络是网络,单独设置是为了让系统与网络之间解耦。
  • 需要客户端每次都能找到服务器进程,所以服务器进程的唯一性不能做任何改变,也就是说端口号是不能随意改变的。因为进程的PID重新启动时会发生变化,所以不能使用它(再说PID本身就不是为网络设定的)。
  • 不是所有进程都要提供网络服务或请求,但是所有进程都需要PID。

4. 通过进程和port,就可以找到对应的网络服务进程,那么底层操作系统是如何根据port找到指定的进程的?通过一种哈希的方式映射找到的。

5. 在网络通信过程中,我们除了要把数据发送给对方,也要把自己的ip和port发送给对方,因为对方给我们发数据时也需要ip和port。

理解源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁"。

认识TCP协议

这里我们先了解认识一下TCP(Transmission Control Protocol 传输控制协议)。。

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

认识UDP协议

这里我们先了解认识一下UDP(User Datagram Protocol 用户数据报协议)。

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

 网络字节序

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

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

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

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

 socket编程接口

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

sockaddr结构

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

  • 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 结构

 sockaddr_in 结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

 in_addr结构

 in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

地址转换函数

本篇文章只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串转in_addr的函数:

in_addr转字符串的函数:

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr。


代码示例:


简单的UDP网络程序

实现一个简单的英译汉的功能

服务器:

udpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <strings.h>
#include <errno.h>
#include <cstring>
#include <functional>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

namespace Server
{
    using namespace std;

    static const string defaultIP = "0.0.0.0";
    static const int gnum = 1024;
    enum { USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,OPEN_ERR};
    typedef function<void(int,string,uint16_t,string)> func_t;

    class udpServer
    {
    public:
        udpServer(const func_t& cb, const uint16_t& port,const string& ip = defaultIP)
            :_callback(cb),_port(port),_ip(ip),_sockfd(-1)
        {}
        void initServer()   //初始化服务器
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr << "socket error: " << errno  << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success: " << " : " << _sockfd << endl;

            //2. 绑定 port,ip
            //未来服务器要明确的port,不能随意改变
            struct sockaddr_in local;   //在栈上定义了一个变量
            bzero(&local,sizeof(local));    //初始化
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);  //把port全部转为大端
            //inet_addr函数会帮我们作两件事:1.将string转为uint32_t, 2. htonl(),主机转网络
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); 
            // local.sin_addr.s_addr = htonl(INADDR_ANY); //bind任意地址,服务器的真实写法

            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n == -1)
            {
                cerr << "bind error: " << errno  << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            //UDP Server 的预备工作完成
        }
        void start()    //运行服务器
        {
            //服务器的本质就是死循环
            char buffer[gnum];
            for(;;)
            {
                //读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                //1. 数据是什么2.谁发的
                if(s > 0)
                {
                    buffer[s] = 0;
                    string Clientip = inet_ntoa(peer.sin_addr);//inet_ntoa该函数做两件事:1.网络转主机,2.整数转string
                    uint16_t Clientport = ntohs(peer.sin_port);
                    string message = buffer;

                    cout << Clientip << "[" << Clientport << "]# "<< message << endl;
                    //我们只把数据打印出来就完了吗?要对数据做处理,回调函数
                    _callback(_sockfd,Clientip,Clientport,message);
                }
            }
        }
        ~udpServer()
        {}
    private:
        uint16_t _port;     //端口号
        string _ip;         //ip
        int _sockfd;        //套接字
        func_t _callback;   //回调
    };
}//namespace end Server

udpServer.cc

#include "udpServer.hpp"

#include <memory>
#include <signal.h>
#include <fstream>
using namespace Server;

const string dictTxt = "./dict.txt";
unordered_map<string,string> dict;
// 提示 运行格式
static void Usage(string proc)
{
    cout<<"Usage:\n\t" << proc <<" local_port\n\n";
}
//切割dict.txt文本内容
static bool cutString(string& target,string* s1,string* s2,const string sep)
{
    auto pos = target.find(sep);
    if(pos == string::npos)
        return false;

    *s1 = target.substr(0,pos); //[)
    *s2 = target.substr(pos+sep.size()); //[)
    return true;
}
//输出字典内容
static void debugPrint()
{
    for(const auto& dt : dict)
    {
        cout << dt.first << " # " << dt.second << endl;
    }
}
//初始化字典
static void initDict()
{
    ifstream in(dictTxt,std::ios::binary);
    if(!in.is_open())
    {
        cerr << "open file " << dictTxt << " error" << endl;
        exit(OPEN_ERR);
    }
    string line;
    string key,value;
    while(getline(in,line))
    {
        // cout << line << endl;
        if(cutString(line,&key,&value,":"))
        {
            dict.insert(make_pair(key,value));
        }
    }
    in.close();
    cout << "load dict success " << endl;
}
//对捕捉的信号做自定义动作,该函数是 不退出就可以更新字典内容
void reload(int signo)
{
    (void)signo;
    initDict(); //初始化字典
}
//1. 一个翻译业务
void handlerMessage(int sockfd,string Clientip,uint16_t Clientport,string message)
{
    //就可以对message进行特定的业务处理,而不关心message怎么来的 -----  Server 通信和业务逻辑解耦
    //婴儿版的业务逻辑
    string response_message;
    auto iter = dict.find(message);
    if(iter == dict.end())
        response_message = "unkown";    //字典里找不到输入内容,就输出不知道
    else
        response_message = iter->second;
    
    //开始返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(Clientport);
    client.sin_addr.s_addr = inet_addr(Clientip.c_str());

    sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
    
    // cout << "服务器翻译结果# " << response_message << endl;
}



//  ./udpServer port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    // string ip = argv[1];

    //1.
    signal(2,reload);//对2号信号做捕捉
    initDict(); //初始化字典
    // debugPrint();

    unique_ptr<udpServer> usvr(new udpServer(handlerMessage,port)); //1.
    // unique_ptr<udpServer> usvr(new udpServer(execCommand,port));     //2.
    // unique_ptr<udpServer> usvr(new udpServer(routemessage,port));    //3.

    usvr->initServer(); //初始化服务器
    usvr->start();      //运行服务器

    return 0;
}

客户端: 

 udpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <errno.h>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

namespace Client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string& serverip,const uint16_t& serverport)
            :_serverip(serverip),_serverport(serverport),_sockfd(-1),_quit(false)
        {}
        void initClient()   //初始化客户端
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr << "socket error: " << errno  << " : " << strerror(errno) << endl;
                exit(2);
            }
            cout << "socket success: " << " : " << _sockfd << endl;

            //2. client 要不要bind?必须要。需不需要程序员自己显示的bind?不需要。
            //  由操作系统自动形成port进行bind,那么OS在什么时候bind,如何bind?
        }

        void run()      //运行客户端
        {
            struct sockaddr_in server;
            memset(&server,0,sizeof(server)); //bzero和memset这两个都可以
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);

            string message;
            while(!_quit)
            {
                cout << "Please Enter# ";
                // cin >> message;
                getline(cin,message);
                // sendto :在套接字上发送消息
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);  //recvfrom: 从套接字接收消息
                ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
                if(n >= 0) buffer[n] = 0;
                cout << "服务器翻译结果# \n" << buffer << endl;
            }
        }
        ~udpClient()
        {}
    private:
        int _sockfd;    
        string _serverip;
        uint16_t _serverport;

        bool _quit;     //循环条件
    };
} //namespace end Client

 udpClient.cc

#include "udpClient.hpp"
#include <memory>

using namespace Client;

// 提示 运行格式
static void Usage(string proc)
{
    cout<<"Usage:\n\t" << proc <<" Server_ip Server_port\n\n";
}
// ./udpClient Server_ip Server_port
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string Serverip = argv[1];
    uint16_t Serverport = atoi(argv[2]);

    unique_ptr<udpClient> uclt(new udpClient(Serverip,Serverport));

    uclt->initClient(); //初始化客户端
    uclt->run();        //运行客户端


    return 0;
}

简易的远程命令行操作 

 在原有的基础上增加一个接口即可

//2. 一个命令行业务
void execCommand(int sockfd,string Clientip,uint16_t Clientport,string cmd)
{   
    //禁止操作的命令
    if(cmd.find("rm") != string::npos || 
    cmd.find("mv") != string::npos || 
    cmd.find("rmdir") != string::npos)
    {
        cerr << Clientip << " : " << Clientport << " 正在做一个非法的操作:" << cmd << endl;
        return;
    }

    string response;
    FILE* fp = popen(cmd.c_str(),"r");  //popen = pipe + fork + exec
    if(fp == nullptr) response = cmd + "exec failed";
    char line[1024];
    while(fgets(line,sizeof(line),fp))
    {
        response += line;
    }
    pclose(fp);

    //开始返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(Clientport);
    client.sin_addr.s_addr = inet_addr(Clientip.c_str());

    sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}

 简易的多人对话

udpServer.cc

//3. 多人对话服务
OnlineUser oluser;  //对象

void routemessage(int sockfd,string Clientip,uint16_t Clientport,string message)
{
    if(message == "online")
        oluser.addUser(Clientip,Clientport);
    if(message == "offline")
        oluser.delUser(Clientip,Clientport);
    if(oluser.isOnline(Clientip,Clientport))
    {
        //消息的路由
        oluser.broadcastMessage(sockfd,Clientip,Clientport,message);
    }
    else
    {
        struct sockaddr_in client;
        bzero(&client,sizeof(client));

        client.sin_family = AF_INET;
        client.sin_port = htons(Clientport);
        client.sin_addr.s_addr = inet_addr(Clientip.c_str());

        string response = "你还没有上线,请先上线!上线请运行:online \n";
        sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
    }
}

udpClient.hpp 

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <errno.h>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

namespace Client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string& serverip,const uint16_t& serverport)
            :_serverip(serverip),_serverport(serverport),_sockfd(-1),_quit(false)
        {}
        void initClient()   //初始化客户端
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr << "socket error: " << errno  << " : " << strerror(errno) << endl;
                exit(2);
            }
            cout << "socket success: " << " : " << _sockfd << endl;

            //2. client 要不要bind?必须要。需不需要程序员自己显示的bind?不需要。
            //  由操作系统自动形成port进行bind,那么OS在什么时候bind,如何bind?
        }
        //线程代码
        static void* readMessage(void* args)
        {
            int sockfd = *(static_cast<int*>(args));
            pthread_detach(pthread_self());

            while(true)
            {
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);  //recvfrom: 从套接字接收消息
                ssize_t n = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
                if(n >= 0) buffer[n] = 0;
                // cout << "服务器翻译结果# " << buffer << endl;
                cout << buffer << endl;
            }
            return nullptr;
        }
        void run()      //运行客户端
        {   //让线程帮我们发送消息
            pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);

            struct sockaddr_in server;
            memset(&server,0,sizeof(server)); //bzero和memset这两个都可以
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);

            string message;
            while(!_quit)
            {
                fprintf(stderr,"Please Enter# ");
                // cin >> message;
                getline(cin,message);
                // sendto :在套接字上发送消息
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

            }
        }
        ~udpClient()
        {}
    private:
        int _sockfd;    
        string _serverip;
        uint16_t _serverport;

        bool _quit;     //循环条件

        pthread_t _reader;
    };
} //namespace end Client

onlineUser.hpp

#pragma once 

#include <iostream>
#include <string>
#include <cstring>
#include <unordered_map>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;
class User
{
public:
    User(const string& ip,const uint16_t& port)
        :_ip(ip),_port(port)
    {}
    ~User()
    {}
    string ip(){ return _ip;}
    uint16_t port() { return _port;}
private:
    string _ip;
    uint16_t _port;
};

class OnlineUser
{
public:
    OnlineUser()
    {}
    void addUser(const string& ip,const uint16_t& port) //用户上线
    {
        string id = ip + "-" + to_string(port);
        users.insert(make_pair(id,User(ip,port)));
    }
    void delUser(const string& ip,const uint16_t& port)//用户下线
    {
        string id = ip + "-" + to_string(port);
        users.erase(id);
    }
    bool isOnline(const string& ip,const uint16_t& port)    //用户是否存在
    {
        string id = ip + "-" + to_string(port);
        return users.find(id) == users.end() ? false:true;
    }
    //给线长上所有用户转发消息
    void broadcastMessage(int sockfd,const string& ip,const uint16_t& port, const string& message)
    {
        for(auto& user:users)
        {
            struct sockaddr_in client;
            bzero(&client,sizeof(client));

            client.sin_family = AF_INET;
            client.sin_port = htons(user.second.port());
            client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());
            string s = ip +"-" + to_string(port) + "# ";
            s += message;
            sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));

        }
    }
    ~OnlineUser()
    {}
private:
    unordered_map<string,User> users;
};

 UDP完整代码:lesson12/1_UDP · 晚风不及你的笑/MyCodeStorehouse - 码云 - 开源中国 (gitee.com)


简单的TCP网络程序

 TCP socket API 详解

  socket():

         socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;如果socket()调用出错则返回-1;应用程序可以像读写文件一样用read/write在网络上收发数据;

参数一:对于IPv4, family参数指定为AF_INET;
参数二:对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
参数三:protocol参数的介绍从略,指定为0即可

  bind():

        服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用bind绑定一个固定的网络地址和端口号;

        bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听
        myaddr所描述的地址和端口号;bind()成功返回0,失败返回-1。

前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

我们的程序中对myaddr参数是这样初始化的:

1. 将整个结构体清零;
2. 设置地址类型为AF_INET;
3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
4. 端口号为SERV_PORT, 我们定义为8080; 

  listen():

        listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接等待状态,如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5),这个参数具体怎么理解在后面的文章会详细说明。
        listen()成功返回0,失败返回-1。

  accept():

        三次握手完成后,服务器调用accept()接受连接;如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;

        addr是一个传出参数,accept()返回时传出客户端的地址和端口号;如果给addr 参数传NULL,表示不关心客户端的地址;
        addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区); 

connect 

        客户端需要调用connect()连接服务器;
        connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
        connect()成功返回0,出错返回-1

简单的TCP 网络通话

tcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h> 
#include <pthread.h>

#include "log.hpp"


namespace Server
{
    // using namespace std;

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    class tcpServer;
    class threadData
    {
    public:
        threadData(tcpServer* self,const int& sock)
            :_self(self),_sock(sock)
        {}
    public:
        tcpServer* _self;
        int _sock;
    };
    class tcpServer
    {
    public:
        tcpServer(const uint16_t& port = gport)
            :_listensock(-1),_port(port)
        {}
        void initServer()
        {
            //1.创建socket文件套接字对象
            _listensock = socket(AF_INET,SOCK_STREAM,0);
            if(_listensock < 0)
            {
                logMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL,"create socket success");

            // 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 = INADDR_ANY;
            if(bind(_listensock,(struct sockaddr*)&local,sizeof(local)) < 0)
            {
                logMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL,"bind socket success");

            //3. 设置socket为监听状态
            if(listen(_listensock,gbacklog) < 0)
            {
                logMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL,"listen socket success");

        }
        void start()
        {
            for( ; ; )  //死循环
            {
                //4. Server获取新链接
                //sock 是用于和client进行通信的
                struct sockaddr_in peer;
                socklen_t peer_len = sizeof(peer);
                int sock = accept(_listensock,(struct sockaddr*)&peer,&peer_len);
                if(sock < 0)
                {
                    logMessage(ERROR,"accept error,next");
                    continue;
                }
                logMessage(NORMAL,"accept a new link success");
                cout << "sock: " << sock <<endl;
                //5. 未来通信就用这个sock,面向字节流的,后面全是文件操作
                // version 1
                 serviceIO(sock);
                 close(sock);    //对于一个已经使用完毕的sock,我们需要关闭它,不然会导致文件描述符泄漏
                
            }
        }
       

        void serviceIO(int sock)
        {
            char buffer[1024];
            while(true)
            {
                //暂时把读到的数据当字符串
                ssize_t n = read(sock,buffer,sizeof(buffer)-1);
                if(n > 0)
                {
                    buffer[n] = 0;
                    cout << "recv message: " << buffer << endl;
                    
                    string outbuffer = buffer;
                    outbuffer += " server[echo]";
                    write(sock,outbuffer.c_str(),outbuffer.size()); //多路转接
                }
                else if(n == 0)
                {
                    // 代表Client退出
                    logMessage(NORMAL,"client quit,me to!");
                    break;
                }
            }
        }
         
        ~tcpServer()
        {}
    private:
        int _listensock;    //不是用来数据通信的,它是监听链接是否到来的,用于获取新链接的
        uint16_t _port;
    } ;
    
}//namespace end Server 

tcpServer.cc

#include "tcpServer.hpp"
#include <memory>

using namespace Server;
// 提示 运行格式
static void Usage(string proc)
{
    cout<<"Usage:\n\t" << proc <<" local_port\n\n";
}
// ./tcpserver local_port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer();
    tsvr->start();

    return 0;
}

tcpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "log.hpp"  //日志信息

#define NUM 1024

namespace Client
{
    // using namespace std;
    class tcpClient
    {
    public:
        tcpClient(const string& serverip,const uint16_t serverport)
            :_sock(-1),_serverip(serverip),_serverport(serverport)
        {}
        void ininClinet()
        {
            //1.创建socket
            _sock = socket(AF_INET,SOCK_STREAM,0);
            if(_sock < 0)
            {
                logMessage(FATAL,"socket create error");//错误日志信息
                exit(2);
            }
            //2.tcp客户端也要bind,但是不用我们显示的bind,OS会帮我们bind的

        }
        void start()
        {

            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)
            {
                logMessage(ERROR,"socket connect error");
            }
            else
            {
                string msg;
                while(true)
                {
                    cout << "Please# ";
                    getline(cin,msg);
                    write(_sock,msg.c_str(),msg.size());

                    char buffer[NUM];
                    int n = read(_sock,buffer,sizeof(buffer)-1);
                    if(n > 0)
                    {
                        //目前把读到的数据当成字符串
                        buffer[n] = 0;
                        cout << "Server回显# " << buffer << endl;
                    }
                    else
                    {
                        break;
                    }

                }
            }
        }
        ~tcpClient()
        {
            if(_sock >= 0) close(_sock);//可写,可不写
        }
    private:
        int _sock;
        string _serverip;
        uint16_t _serverport;
    };

}//namespace Client end

tcpClient.cc


#include "tcpClient.hpp"
#include <memory>

using namespace Client;

// 提示 运行格式
static void Usage(string proc)
{
    cout<<"Usage:\n\t" << proc <<" server_ip server_port\n\n";
}
// ./tcpClient server_ip_ip server_port
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<tcpClient> tpcl(new tcpClient(serverip, serverport));
    tpcl->ininClinet();
    tpcl->start();

    return 0;
}

再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信.原因是因为我们accecpt了一个请求之后, 就在一直while循环尝试read, 没有继续调用到accecpt, 导致不能接受新的请求.
当前的这个TCP, 只能处理一个连接, 这是不科学的.

 多进程版

只需要将tcpServer.hpp 中的第5 进行修改即可。

                //5. 未来通信就用这个sock,面向字节流的,后面全是文件操作
                // version 1
                // serviceIO(sock);
                // close(sock);    //对于一个已经使用完毕的sock,我们需要关闭它,不然会导致文件描述符泄漏
                //------------------------------------------------------------------------------------
                //version 2.1 多进程版
                pid_t id = fork();
                if(id == 0) //子进程
                {
                    close(_listensock);
                    if(fork() > 0) exit(0); //让孙子进程执行代码,子进程退出被父进程回收,孙子进程会变成孤儿进程

                    serviceIO(sock);
                    close(sock);
                    exit(0);
                }
                //父进程
                pid_t ret = waitpid(id,nullptr,0);
                close(sock);
                if(ret > 0)
                {
                    cout << "wait success: " << ret << endl;
                }
                //------------------------------------------------------------------------------------
                //version 2.2 多进程版 信号方式
                signal(SIGCHLD,SIG_IGN);    //信号忽略,忽略对子进程的管理

                pid_t id = fork();
                if(id == 0) //子进程
                {
                    close(_listensock);

                    serviceIO(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);
                //------------------------------------------------------------------------------------

 改成多进程版后就不存在上面的问题了。

 多线程版

    class tcpServer;    //声明
    class threadData
    {
    public:
        threadData(tcpServer* self,const int& sock)
            :_self(self),_sock(sock)
        {}
    public:
        tcpServer* _self;
        int _sock;
    };

-------------------------------
下面是 class tcpServer 里的内容
        void start()
        {
            for( ; ; )  //死循环
            {
                //4. Server获取新链接
                //sock 是用于和client进行通信的
                struct sockaddr_in peer;
                socklen_t peer_len = sizeof(peer);
                int sock = accept(_listensock,(struct sockaddr*)&peer,&peer_len);
                if(sock < 0)
                {
                    logMessage(ERROR,"accept error,next");
                    continue;
                }
                logMessage(NORMAL,"accept a new link success");
                cout << "sock: " << sock <<endl;
                //5. 未来通信就用这个sock,面向字节流的,后面全是文件操作
         
                // //version 3 多线程版
                pthread_t tid;
                threadData* td = new threadData(this, sock);
                pthread_create(&tid,nullptr,thread_routinue,td);    //创建线程

                
            }
        }
        static void* thread_routinue(void* args)
        {
            pthread_detach(pthread_self());//线程分离
            threadData* td = static_cast<threadData*>(args);
            td->_self->serviceIO(td->_sock);
            delete td;
            close(td->_sock);

            return nullptr;
        }

单例线程池版+守护进程:

完整代码:lesson12/4_TCP(单例线程池) · 晚风不及你的笑/MyCodeStorehouse - 码云 - 开源中国 (gitee.com)

我们在登陆一个服务器时,它会为我们创建一个会话,一个会话中有且只能有一个前台任务,同时还有多个后台任务,任务是可以进行前后台转换的(fg,bg)。这些任务是会受到用户登录和注销影响的。也就是说我们写的服务器,我们注销以后其他人就访问不了了,这样是有问题的,创造者的登录登出应该对服务器不会产生影响,所以就需要守护进程。

守护进程也叫精灵进程,本质是孤儿进程的一种,它是某一个进程单独出来,自成一个会话,自成一个进程组,和终端设备无关。


TCP协议通讯流程

 下图是基于TCP协议的客户端/服务器程序的一般流程:

服务器初始化:

  1. 调用socket, 创建文件描述符;
  2. 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  3. 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
  4. 调用accecpt, 并阻塞, 等待客户端连接过来;

建立连接的过程:

  1. 调用socket, 创建文件描述符;
  2. 调用connect, 向服务器发起连接请求;
  3. connect会发出SYN段并阻塞等待服务器应答; (第一次);
  4. 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次);
  5. 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次);

用户发起链接请求后,链接的建立是由双方的操作系统自动完成的。

        这个建立连接的过程, 通常称为 三次握手。

数据传输的过程

  1. 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
  2. 服务器从accept()返回后立刻调用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
  3. 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
  4. 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;
  5. 客户端收到后从read()返回, 发送下一条请求,如此循环下去;

断开连接的过程:

  1. 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
  2. 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
  3. read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
  4. 客户端收到FIN, 再返回一个ACK给服务器; (第四次)

同样,断开连接的过程也是双方操作系统自动完成的,我们只能决定什么时候开始建立链接和断开连接

        这个断开连接的过程, 通常称为 四次挥手。

所谓的链接说白了就是操作系统内部创建出来的链接结构体,它里面一定包含了链接在建立时链接相关的属性信息。那么服务端一定会有大量的链接到来,每到来一个链接,服务端就构建一个链接对象,然后将所有的链接对象在内核当中用特定的数据结构(比如链表)管理起来,至此就完成了对链接们建模的过程。

TCP 和 UDP 对比

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流 vs 数据报

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux— 网络编程套接字 的相关文章

  • 内核如何区分线程和进程

    Linux 中的线程被称为轻量级进程 无论是进程还是线程 它们的实现都是通过task struct数据结构 1 gt 那么 从这个意义上说 内核如何区分线程和进程 2 gt 当发生上下文切换时 线程如何在上下文切换中获得更少的开销 因为在此
  • 保护一个保存 MySQL 数据库的简单 Linux 服务器?

    这是一个初学者问题 但我浏览了该网站上的许多问题 但没有找到简单直接的答案 我正在设置一个运行 Ubuntu 的 Linux 服务器来存储 MySQL 数据库 该服务器尽可能安全非常重要 据我所知 我主要担心的是传入的 DoS DDoS 攻
  • 如何在Linux中打开端口[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我已经安装了 Web 应用程序 该应用程序在 RHEL centOS 上的端口 8080 上运行 我只能通过命令行访问该机器 我尝试从我的
  • 为什么我的 Java 代码执行 bash 命令不正确?

    我试图让我的 Java 程序与 Linux bash 交互 但出了问题 我有一个简单的可执行文件prog从中读取一个整数stdin并输出其平方 执行中 echo 5 prog 从 bash 本身打印正确答案25 in stdout但运行 i
  • 在 UNIX 时间戳 Shell/Bash 中将日期与时区转换

    我需要将日期从格式为 yyyy mm dd hh mm ss TZ 的字符串转换为 UNIX 时间 TZ 时区 到目前为止我所做的是将没有时区的 yyyy mm dd hh mm ss 格式的日期转换为时间戳 dateYMD 2019 2
  • 从 bash 脚本返回值

    我想创建一个返回值的 Bash 文件 意思是 在脚本 script a bash 中我有一定的计算 脚本 script b bash 会调用它 script a bash return 1 5 script b bash a value s
  • 在类中使用静态互斥体

    我有一个可以有很多实例的类 它在内部创建并初始化来自第三方库 使用一些全局变量 的一些成员 并且不是线程安全的 我考虑过使用 static boost mutex 它将被锁定在我的类构造函数和析构函数中 因此 在我的线程中创建和销毁实例对于
  • 使用openssl从服务器获取证书

    我正在尝试获取远程服务器的证书 然后可以将其添加到我的密钥库中并在我的 Java 应用程序中使用 一位高级开发人员 正在度假 告诉我我可以运行这个 openssl s client connect host host 9999 获取转储的原
  • Web 本地应用程序 Apache:运行 shell 脚本

    我开发了一个 shell 脚本 我想用它创建一个 UI 我决定使用带有本地服务器的 Web 界面 因为我对 HTML PHP 的了解很少 比 QT 或 Java 的了解更多 我只是希望我的 html 可以在我的计算机上运行 shell 脚本
  • 在linux x86平台上学习ARM所需的工具[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我有一个 x86 linux 机器 在阅读一些关于 ARM 的各种信息时 我很好奇 现在我想花一些时间学
  • 套接字发送并发保证

    如果我在两个进程 或两个线程 之间共享一个套接字 并且在这两个进程中我尝试发送一条阻塞的大消息 大于下划线协议缓冲区 是否可以保证这两个消息将按顺序发送 或者消息可以在内核内部交错吗 我主要对 TCP over IP 行为感兴趣 但了解它是
  • 每当调用 malloc/free 时输出到 stderr

    使用 Linux GCC C 每当调用 malloc free new delete 时 我想向 stderr 记录一些内容 我试图了解库的内存分配 因此我想在运行单元测试时生成此输出 我使用 valgrind 进行内存泄漏检测 但我找不到
  • bash 别名中允许使用哪些字符[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我最近添加了 alias cd alias cd alias cd 到我的 bash aliases 文件 玩弄这个 我注意到在别名时 被
  • 模拟用户输入以使用不同参数多次调用脚本

    我必须使用提供的脚本 该脚本在脚本运行时接受用户输入而不是参数 我无法解决这个问题 脚本的一个例子是 bin bash echo param one read one doSomething echo param two read two
  • 从汇编程序获取命令行参数

    通读 专业汇编语言书籍 似乎它提供了用于读取命令行参数的错误代码 我纠正了一点 现在它从段错误变成了读取参数计数 然后是段错误 这是完整的代码 data output1 asciz There are d params n output2
  • 从命名管道读取

    我必须实现一个 打印服务器 我有 1 个客户端文件和 1 个服务器文件 include
  • 将用户添加到组但运行“id”时未反映

    R 创建了一个名为 Staff 的组 我希望能够在不以 sudo 身份启动 R 的情况下更新软件包 所以我使用以下方法将自己添加到员工中 sudo usermod G adm dialout cdrom plugdev lpadmin ad
  • libusb 和轮询/选择

    我正在使用 Linux 操作系统 想知道是否有任何文件描述符可以轮询 选择 当数据等待从 USB 设备读取时会触发这些文件描述符 我也在使用 libusb 库 但尚未找到可以使用的文件描述符 Use libusb 的轮询函数 http li
  • 如何修改s_client的代码?

    我正在玩apps s client c in the openssl源代码 我想进行一些更改并运行它 但是在保存文件并执行操作后 我的更改没有得到反映make all or a make 例如 我改变了sc usage函数为此 BIO pr
  • 当存在点和下划线时,使用 sed 搜索并替换

    我该如何更换foo with foo sed 只需运行 sed s foo foo g file php 不起作用 逃离 sed s foo foo g file php Example cat test txt foo bar sed s

随机推荐

  • PyTorch实现YOLOv3

    1 网络结构 左边Darknet网络结构 右边YOLOv3网络结构 详细解析可参考链接 2 pytorch代码实现 darknet53 py coding utf 8 Time 2020 10 20 下午10 17 Author zxq F
  • 关于static_cast的一个问题

    写树的插入代码时 发现了一个问题 void insert Tree tree int value Node node malloc sizeof Node node gt data value node gt left NULL node
  • powerdesigner如何创建外键

    先创建两个entity 因为是多个t主题 topic 对应一个作者 author 所以topic要有个外键author id参照author的id 那么双击topic 创建一个column author id 并且在M上选对勾 意思是这个外
  • CTF工具隐写分离神器Binwalk安装和详细使用方法

    binwalk安装 1 Binwalk 介绍 2 Binwalk下载 3 Windows安装 4 Linux下载安装 5 Binwalk基本用法 6 Binwalk案例展示 7 Binwalk总结 1 Binwalk 介绍 Binwalk
  • 编译出现 error LNK2001: 无法解析的外部符号 “public: virtual struct QMetaObject const * __thiscall Widget::metaObj

    出现这种情况的原因是 可能是以下几种 1 h中是否加上了Q OBJECT宏 2 你的类是否继承了QObject这个类 3 你的这个类确定在 h中进行了声明 然后在 cpp中进行了 4 注意这个信号和槽是后面加入的话 注意你的moc文件是否生
  • 【IDEA】Git切换分支checkout后,没有maven/maven失效/找不到maven项目

    解决办法1 找到项目pom xml 右键选择add as a maven project 导入maven 解决办法2 在terminal命令行mvn clean清除maven 再安装mvn install Dmaven test skip
  • 路由器默认密码

    3COM CellPlex 7000 Telnet tech tech 3COM CoreBuilder 7000 6000 3500 2500 Telnet debug synnet 3COM CoreBuilder 7000 6000
  • C语言编程示例之Hello World!

    C语言编程示例之Hello World include
  • kubernetes之证书更新

    证书更新 kubernetes的证书存放在 etc kubernetes pki目录下 使用kubeadm alpha certs check expiration 可查看证书有效时间 可以看出apiserver等证书有效期为一年 ca等证
  • 区块链ETH--remix简单使用介绍

    新版本下的remix使用介绍 1 Solidity与智能合约 起源于以太坊 Ethereum 设计的目的是能在以太坊虚拟机 EVM 上运行 Solidity 是一门面向合约的 为实现智能合约而创建的高级编程语言 Solidity文档 htt
  • 使用curl命令操作elasticsearch、使用http 查询ES

    使用curl命令操作elasticsearch And 使用http 查询ES 第一 cat系列 cat系列提供了一系列查询elasticsearch集群状态的接口 你可以通过执行 curl XGET localhost 9200 cat
  • Mac安装Java环境

    打开Mac终端 输入java version 检查当前Java环境 显示没有Java环境 下载安装jdk 默认配置即可 安装完成后 再次检查Java环境 已识别到所安装的Java环境 检查Java安装目录 有2个 且默认使用Plug Ins
  • Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)

    原文地址 http blog csdn net congcong68 article details 41113239 互联网的发展 网站应用的规模不断扩大 常规的垂直应用架构已无法应对 分布式服务架构以及流动计算架构势在必行 Dubbo是
  • Java的多重循环和程序调试

    1 掌握Java二重循环 多重 嵌套 注意 外层循环控制行 内层循环控制列 每行打印的内容 外层循环执行一次 内层循环执行一遍 一般多重循环值的就是二重循环 2 使用跳转语句控制程序的流程 return结束程序 结束当前的方法 返回到方法的
  • 补偿策略-刷新创建失败的禅道工单

    背景 当平台创建工单时需要将数据同步到第三方 由于某些原因 如网络延迟没有收到响应 但平台工单需要正常创建 此时需要创建异步补偿策略 1 创建补偿任务 Slf4j public class QueryZenTaoRunable extend
  • 分布式事务的几种解决方案

    一 基础概念 1 什么是事务 事务可以看做是一次大的活动 它由不同的小活动组成 这些活动 要么全部成功 要么全部失败 2 本地事务 在计算机系统中 更多的是通过 关系型数据库来控制事务 这是利用数据库 本身的事务特性来实现的 因此叫 数据库
  • 【干货】Redis在Java开发中的基本使用和巧妙用法

    Redis是一款高性能的内存数据结构存储系统 能够支持多种数据结构类型 如字符串 哈希 列表 集合 有序集合等 也能够支持高级功能 如事务 发布 订阅 Lua脚本等 具有高可用性 高并发性和可扩展性的优点 在Java开发中 Redis可以作
  • Javascript编程语言-现代模式,“use strict“,变量,命名变量,常量,保留字

    现代模式 use strict 长久以来 JavaScript 不断向前发展且并未带来任何兼容性问题 新的特性被加入 旧的功能也没有改变 这么做有利于兼容旧代码 但缺点是 JavaScript 创造者的任何错误或不完善的决定也将永远被保留在
  • sqlmap的使用 (以封神台题目为例)

    一 sqlmap选项 目标 至少要选中一个参数 u URL url URL 目标为 URL 例如 http www site com vuln php id 1 g GOOGLEDORK 将谷歌dork的结果作为目标url 请求 这些选项可
  • Linux— 网络编程套接字

    目录 预备知识 认识端口号 理解源端口号和目的端口号 认识TCP协议 认识UDP协议 网络字节序 socket编程接口 socket 常见API sockaddr结构 sockaddr 结构 编辑 sockaddr in 结构 in add