socket编程

网络字节序

网络字节序是指多字节变量在网络传输时的表示方法,它采用大端字节序的表示方式。大端字节序是指将高序字节存储在起始地址,而低序字节存储在后续地址。

网络字节序和主机字节序的转化

  • htonl(host to network long):将unsigned long类型的主机字节序转换为网络字节序。
  • htons(host to network short):将unsigned short类型的主机字节序转换为网络字节序。
  • ntohl(network to host long):将unsigned long类型的网络字节序转换为主机字节序。
  • ntohs(network to host short):将unsigned short类型的网络字节序转换为主机字节序。

socket

Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。既然是文件就可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

image-20240512175503393

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

特别注意inet_addr

inet_addr是一个库函数,用于将点分十进制的IP地址字如:192.168.1.1转换为一个32位的无符号整数(通常是网络字节序)。如果输入的字符串不是一个有效的IP地址,inet_addr会返回INADDR_NONE通常是(in_addr_t)-1。

server.sin_addr.s_addr=inet_addr(ser_ip.c_str()); 

sin_addr是sockaddr_in结构体中的一个in_addr结构体成员,用于存储IPv4地址。s_addr是in_addr结构体中的一个成员,它是一个无符号整数,用于存储IP地址(作为32位无符号整数)。

整行代码server.sin_addr.s_addr=inet_addr(ser_ip.c_str());的意思是:将ser_ip字符串(一个IPv4地址)转换为32位无符号整数,并将这个整数存储在serversin_addr.s_addr成员中。

sockaddr

struct sockaddr {
    //IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 
	sa_family_t sa_family; 		/* address family, AF_xxx */
	char sa_data[14];			/* 14 bytes of protocol address */
};
//sa_data:这是一个14字节的字段,用于存储具体的地址信息。
//但是,由于这个字段是固定的长度,并且不直接支持IPv6等更复杂的地址类型,因此在实践中很少直接使用sockaddr结构体,而是使用更具体的结构体(如sockaddr_in或sockaddr_in6)。

sockaddr_in结构

IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址。在编写网络程序时,通常会将sockaddr_in结构体转换为sockaddr指针,然后将其作为参数传递给系统调用(如bind、connect等)。这是因为这些系统调用使用sockaddr指针作为参数,以支持多种协议族。

image-20240512180243664

UDP C/S

udp_server.cc

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

//const uint16_t port  =8080;
using namespace std;

string Usage(string prot)
{
    cout<<"Usage:"<<prot<<"port"<<endl;
}

// ./udp_server prot
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[1]); return 1;
    }
    uint16_t prot=atoi(argv[1]);// uint16_t --16个二进制位


    //1.创建socket,打开网络文件
    //AF_INET--IPv4 
    //SOCK_DGRAM---Supports datagrams (connectionless, unreliable messages of a fixed maximum length)
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    //2.给服务器绑定端口和IP
    struct sockaddr_in local; //IPv4地址用sockaddr_in结构体表示
    local.sin_family=AF_INET;
    local.sin_port=htons(prot);//主机序列转化为网络序列

    //local.sin_addr.s_addr   1.将人识别的点分10进制字符串风格的IP转化为4字节整数IP
    //这个函数解决两个问题  2.大小端问题
    local.sin_addr.s_addr=INADDR_ANY;//如果设置成某个IP,意味着只有发到这个IP的数据才会交到网络进程,这里需要所有发到该主机端口的数据

    if(bind(sock,(struct sockaddr* )&local,sizeof(local))<0)
    {
        cerr<<"bind error"<<errno<<endl; return 2;
    }

    //3.提供服务 shell
    bool quit=false;
    char buffer[1024];
    while(!quit)
    {
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        //recvfrom的作用:receive messages from a socket
        size_t cnt=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
        if(cnt>0)
        {
            buffer[cnt]=0;
            FILE* fp=popen(buffer,"r");//popen()函数通过创建管道、分叉和调用shell来打开进程
            string echo_hello;
            char line[1024]={0};

            while(fgets(line,sizeof(line),fp)!=NULL)
            {
                echo_hello+=line;
            }
            pclose(fp);
            cout<<"client# "<<buffer<<endl;
            //transmit a message to another socket.
            sendto(sock,echo_hello.c_str(),echo_hello.size(),0,(struct sockaddr*)&peer,len);
        }
        else{}
       
    }
    return 0;
}

udp_client.cc

#include<iostream>
#include<string>
#include<cerrno>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
using namespace std;
void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<"server_ip server_port"<<endl;
}

// ./udp_client  sercer_ip server_proc
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[3]);return 0;
    }

    //1.创建socket,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        cerr<<"socket error:"<<errno<<endl; return 1;
    }
      
    //客户端不需要自己绑定,client发消息的时候OS会自动绑定,采用随机端口的方式

    //和谁发消息
    struct sockaddr_in server;
    server.sin_family=AF_INET; //IPV4
    server.sin_port=htons(atoi(argv[2]));//先转整数再转为网络序列
    server.sin_addr.s_addr=inet_addr(argv[1]);//server的IP

    while(1)
    {
        // string message;
        // cout<<"输入#:";
        // cin>>message;
        cout<<"my shell $:";
        char line[1024];
        fgets(line,sizeof(line),stdin);
        //给服务器发送消息
        sendto(sock,line,strlen(line),0,(struct sockaddr*)&server,sizeof(server));

        struct sockaddr_in tmp;
        socklen_t len=sizeof(tmp);
        char buffer[1024];
        size_t cnt=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len);
        if(cnt>0)
        {
            //在网络通信中,只有报文的大小/字节流中字节的个数,没有字符串的概念
            buffer[cnt]=0;
            cout<<buffer<<endl;
        }else{

        }
    }
    return 0;

}

TCP C/S

tcp_server.cc

// ./tcp_server prot
int main(int argc,char* argv[])
{
    if(argc!=2){
        Usage(argv[1]); return 1;
    }

    //1.创建套接字
    // socket本质是打开文件,仅仅有系统相关的内容
    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock<0)
    {
        cerr<<"socktet error"<<errno<<endl; return 2;
    }

    //2.bind -- ip+port和文件信息关联--- 写入sockaddr中
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_addr.s_addr=INADDR_ANY;
    local.sin_port=htons(atoi(argv[1]));

    if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        cerr<<"bind error"<<errno<<endl; return 3;
    }

    //3.等待链接
    //套接字设置listen状态本质是:不断的给用户提供一个建立连接的功能
    const int back_log=3;//back_log -的挂起连接队列可能增长到的最大长度
    if(listen(listen_sock,back_log)<0)
    {
        cerr<<"listen error"<<endl; return 4;
    }

    // signal(SIGCHLD,SIG_IGN);//在Linux中父进程忽略子进程的SIGCHLD信号,子进程会自动退出释放资源
    //4.链接
    for(;;)
    {
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        int new_sock = accept(listen_sock,(struct sockaddr*)&peer,&len);//获取连接到应用层
        if(new_sock<0) continue;
        //版本1.
        //ServiceIO(new_sock); 单线程版本没人用

        uint16_t cli_port=ntohs(peer.sin_port); //网络转主机序列
        string cli_ip=inet_ntoa(peer.sin_addr);//主机地址(以网络字节顺序给出)转换为
                                               //IPv4点分十进制格式的字符串

        cout<<"get a new link: "<<cli_port<<"-"<<cli_ip<<endl;

        //版本3.
        Task t(new_sock);
        ThreadPool<Task>::GetInstance()->PushTask(t);
        
        //版本2.
        // pid_t id=fork();
        // if(id<0){
        //     continue;
        // }
        // //父进程打开的fd子进程会继承下去,父子进程中的哪个都要关闭不用的文件描述符
        // else if(id==0)// child
        // {
        //     close(listen_sock);

        //     //用这个不需要等待
        //     //退出的是子进程,子进程刚创建出来就退出了
        //     if(fork()>0)exit(0);

        //     //向后走的是孙子进程,爷孙进程不需要等待
        //     ServiceIO(new_sock);
        //     close(new_sock);//不关闭文件描述符,会造成文件描述符泄漏的问题
        //     exit(0);
        // }
        // else//father
        // { 
        //     waitpid(id,nullptr,0); //等待子进程
        //     close(new_sock);
        // }
    }
    return 0;
}

tcp_client.cc

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

using namespace std;
void Usage(string proc)
{
    cout<<"Usage:"<<proc<<"server_ip server_prot"<<endl;
}

// ./tcp_client server_ip server_prot
int main(int argc, char* argv[])
{
    if(argc!=3){
        Usage(argv[2]); return 1;
    }
    string ser_ip=argv[1];
    uint16_t ser_port=(uint16_t)atoi(argv[2]);

    //1.创建socket
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0){
        cerr<<"socket error"<<endl; return 2;
    }

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;

    //(1). 将点分十进制的字符串风格的IP,转化成为4字节IP
    //(2). 将4字节由主机序列转化成为网络序列
    server.sin_addr.s_addr=inet_addr(ser_ip.c_str());
    server.sin_port=htons(ser_port);

    //2.connect
    //系统层面构建一个请求报文发送出去
    //网络层面 tcp链接的三次握手
    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
    {
        cerr<<"connect error"<<endl; return 3;
    }
    cout<<"connet success!!!"<<endl;

    while(true)
    {
       cout<<"Please Enter#";
       char buffer[1024];
       fgets(buffer,sizeof(buffer)-1,stdin);

       write(sock,buffer,strlen(buffer)); 
       ssize_t s=read(sock,buffer,sizeof(buffer)-1);

       if(s>0){
        buffer[s]=0;
        cout<<"server echo"<<buffer<<endl;
       }
    }
    return 0;

}
  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值