Linux网络套接字

1. Linux网络编程接口

1.1 套接字的文件描述符

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain:设置通讯的协议。可选宏定义name:

NamePurposeMan Page
AF_UNIX, AF_LOCAL本地unix(7)
AF_INETIPv4ip(7)
AF_INET6IPv6ipv6(7)
AF_IPXIPX
AF_PACKET低级数据包接口packet(7)

type:指定通信语义

Typedescription
SOCK_STREAM提供有序、可靠、双向、基于连接的字节流。可以支持带外数据传输机制(全双工,类似于管道)TCP
SOCK_DGRAM支持数据报(固定最大长度的无连接、不可靠消息)。UDP
SOCK_SEQPACKET为固定最大长度的数据报提供有序、可靠、基于双向连接的数据传输路径;消费者需要在每次输入系统调用时读取整个数据包。
SOCK_RAW提供原始网络协议访问。
SOCK_RDM提供一个可靠的数据报层,该层不保证顺序。
SOCK_PACKET过时(不再使用)
SOCK_NONBLOCK在新打开的文件描述上设置O_NONBLOCK标志。使用此标志可以节省对fcntl(2)的额外调用,却能获得相同的结果。
SOCK_CLOEXEC在新文件描述符上设置close-on-exec(FD_CLOEXEC)标志。参阅open(2)

protocol:指定要与套接字一起使用的特定协议,通常设为0;

返回值:成功返回文件描述符fd;失败返回-1.

1.2 IP地址和端口与进程(套接字)的绑定

使用sockaddr_in初始化绑定的地址和端口号,端口号16bit,IPV4通常是32bit的数据。

#include <netinet/in.h>


#define __SOCK_SIZE__   16      /* sizeof(struct sockaddr)  */
struct sockaddr_in {                                                                                                                                    
  __kernel_sa_family_t  sin_family; /* Address family       */
  __be16        sin_port;   /* Port number          */
  struct in_addr    sin_addr;   /* Internet address     */
    /* Pad to size of `struct sockaddr'. */
  unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -
            sizeof(unsigned short int) - sizeof(struct in_addr)];
};

struct in_addr {                                        
    __be32  s_addr;
};
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:需要绑定的文件描述符

addr:给套接字分配的地址。

addrlen :由addr指向的地址结构体字节长度。

返回值:成功返回0;失败返回-1.

1.3 发送和接收的接口函数

发送和接收本质是读取和写入发送和接收缓冲区,使用readwrite也能达到类似的效果。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
            struct sockaddr *src_addr, socklen_t *addrlen);  //UDP

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

recv通常仅在连接的套接字上使用;recvfromrecvmsg用于从套接字接收消息和数据,无论其是否面向连接。所有函数返回:成功返回消息的长度,若提供的buf装不下, 超出的字节可能会忽略。若套接字消息为空,则接受阻塞等待(若为非阻塞状态,返回为-1)

  • recvfrom

src_addr: (struct sockaddr *)sockaddr_in

flags: 0 为阻塞式读取。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);   //UDP

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

send调用只能在套接字处于连接状态时使用(以便知道预期的收件人)。sendwrite之间的唯一区别是是否存在标志。对于0标志参数,send相当于writesend(sockfd, buf, len, flags); = sendto(sockfd, buf, len, flags, NULL, 0);

对于sendsendto,消息位于buf中,长度为len。对于sendmsg,消息由数组msg的元素msg.msg_iov指向。调用还允许发送辅助数据(也称为控制信息)。如果消息太长,无法通过基础协议进行原子传递,则返回错误EMSGSIZE,并且不会传输消息。

1.4 数据格式的转换

由于从网络到主机的数据存在大端小端、字符字节长度等数据格式的问题,需要将数据转化为特定的格式。这个过程可以更具当前的系统自己实现,当然也可以调用系统提供的函数接口。

网络上的数据传输一般是大端模式(即数据低字节存在内存高地址),数据的发送从内存的低地址到高地址,这样在发送的时候会先发数据的高位字节(报头),这样接受端可以快速判断数据的正负和大小。

  • 字符类型的转换
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong); //将无符号整数hostlong从主机字节顺序转换为网络字节顺序

uint16_t htons(uint16_t hostshort); //将短整数hostshort从主机字节顺序转换为网络字节顺序。

uint32_t ntohl(uint32_t netlong); //无符号整数hostlong   网络——>主机

uint16_t ntohs(uint16_t netshort); //短整数hostshort  网络——>主机
  • 主机字节序和网络字节序的转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//*****
int inet_aton(const char *cp, struct in_addr *inp); 
//将Internet主机地址cp从IPv4点分十进制形式转换为二进制形式(按网络字节顺序)并存储它位于inp指向的结构中。如果地址有效,inet_aton()返回非零;如果地址无效,则返回零。cp 的地址具有以下形式之一:a.b.c.d、 a.b.c、 a.b、 a

//*****
in_addr_t inet_addr(const char *cp);
//函数将Internet主机地址cp从IPv4点分十进制形式转换为网络字节顺序的二进制数据(主机到网络)


in_addr_t inet_network(const char *cp);
//将IPv4点分十进制形式的字符串cp, 转换为适合用作Internet网络地址的主机字节序。成功后,将返回转换后的地址。如果输入无效,则返回-1。

char *inet_ntoa(struct in_addr in);  //线程不安全 推荐inet_ntop,提供缓冲区保存结果、
//将以网络字节顺序形式的Internet主机地址,转换为IPv4点分十进制表示法的字符串。(网络到主机)

in_addr_t inet_lnaof(struct in_addr in);
//返回Internet地址in中的本地网络地址部分。返回的值按主机字节顺序排列。

in_addr_t inet_netof(struct in_addr in);
//返回Internet地址in中的网络号部分。返回的值按主机字节顺序排列。

struct in_addr inet_makeaddr(int net, int host);
//与inet_lnaof和inet_netof相反。将网络编号net与本地主机地址host组合在一起按主机字节顺序创建的网络主机地址,以网络字节序返回

1.5 tcp相关函数接口

由于tcp在数据传输前需要建立连接,对于服务器端需要listen监听和accept接受请求,对于客户端需要connect发送连接请求。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

backlog参数定义sockfd的挂起连接队列可能增长到的最大长度。如果连接请求队列已满时,客户端的请求会收到一个错误。

成功返回0;失败返回-1.

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

成功返回一个文件描述符,失败返回-1.

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect系统调用将文件描述符sockfd引用的套接字连接到addr指定的地址。

如果sockfd的类型为SOCK_DGRAM,那么addr是默认情况下数据报发送到的地址,也是数据报收到的唯一地址。如果套接字的类型为SOCK_STREAM 或 SOCK_SEQPACKET,则此调用将尝试与addr指向的地址建立链接。

返回值:链接成功返回0;失败返回-1.

2. 两个例子

2.1 UDP

  • udpserver.cpp
#include <iostream>
#include <string>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>

class udpserver
{
public:
    udpserver(const int& _port = 6666)
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // IPV4 UDP
        if(_sockfd == -1 )
        {
            std::cout << "socket create faild" << std::endl;
            exit(-1);
        }
        _local.sin_addr.s_addr = htonl(INADDR_ANY);   //开放所有的ip,INADDR_ANY宏定义为0
        _local.sin_family = AF_INET;
        _local.sin_port = htons(_port);
        memset(&_local.sin_zero, 0, sizeof(_local.sin_zero));

        if(bind(_sockfd, (struct sockaddr*)&_local, sizeof(_local)) == -1)
        {
            std::cout << "socket bind faild" << std::endl;
            exit(-2);
        }
    }

    void StartServer()
    {
        char buf[1024] = {0};
        while(1)
        {
            struct sockaddr_in RemoteIp;
            socklen_t len = sizeof(RemoteIp);
            std::cout << "receive from client# ";
            fflush(stdout);

            ssize_t size = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&RemoteIp, &len);
            if(size > 0)
            {
                buf[size] = '\0';
                int port = ntohs(RemoteIp.sin_port);
                std::string addr = inet_ntoa(RemoteIp.sin_addr);
                std::cout << addr << ":" << port << " Says-> " << buf << std::endl;
            }
        }
    }
    ~udpserver()
    {
        close(_sockfd);
    }

private:
    short _port;
    int _sockfd;
    struct sockaddr_in _local;
};

int main()
{
    udpserver* udpS = new udpserver;
    udpS->StartServer();
    return 0;
}
  • udpclient.cpp
#include <iostream>
#include <string>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>

class udpclient
{
public:
    udpclient(const std::string &serverIP = "127.0.0.1", const int &port = 6666)
        :_serverIP(serverIP)
        ,_serverPort(port)
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // IPV4 UDP
        if(_sockfd == -1 )
        {
            std::cout << "socket create faild" << std::endl;
            exit(-1);
        }
    }

    void StartClient()
    {
        while(1)
        {
            std::string buf;
            std::cout << "Message Send$ ";
            fflush(stdout);

            std::cin >> buf;

            struct sockaddr_in Send2Remote;
            Send2Remote.sin_addr.s_addr = inet_addr(_serverIP.c_str());
            Send2Remote.sin_family = AF_INET;
            Send2Remote.sin_port = htons(_serverPort);
            memset(&Send2Remote.sin_zero, 0, sizeof(Send2Remote.sin_zero));

            socklen_t len = sizeof(Send2Remote);
            sendto(_sockfd, buf.c_str(), buf.size(), 0, (struct sockaddr*)&Send2Remote, len);

        }
    }

    ~udpclient()
    {
        close(_sockfd);
    }

private:
    std::string _serverIP;
    int _serverPort;
    int _sockfd;
};

int main()
{
    udpclient* udpC = new udpclient;
    udpC->StartClient();
    return 0;
}

2.2 TCP

  • tcpserver.cpp
#include <iostream>
#include <string>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>

class tcpserver
{
public:
    tcpserver(const int& _port = 1666)
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0); // IPV4 UDP
        if(_sockfd == -1 )
        {
            std::cout << "socket create faild" << std::endl;
            exit(-1);
        }
        _local.sin_addr.s_addr = htonl(INADDR_ANY);   //开放所有的ip,INADDR_ANY宏定义为0
        _local.sin_family = AF_INET;
        _local.sin_port = htons(_port);
        memset(&_local.sin_zero, 0, sizeof(_local.sin_zero));

        if(bind(_sockfd, (struct sockaddr*)&_local, sizeof(_local)) == -1)
        {
            std::cout << "socket bind faild" << std::endl;
            exit(-2);
        }

        //建立连接
        if(listen(_sockfd, 10) < 0)
        {
            std::cout << "listen failed" << std::endl;
            exit(-3);
        }
    }

    void MyService(const std::string &ip, const int &port)
    {
        char buf[1024] = {0};
        while(1)
        {
            ssize_t size = read(_Remotefd, buf, sizeof(buf) - 1);
            if(size > 0)  //读到了size个字符
            {
                buf[size] = '\0';
                std::cout << "received from client(" << ip << ":" << port << ")# " << buf << std::endl;
                write(_Remotefd, buf, size);

            }
            else if(size == 0) //读到了文件的结尾,对端关闭了连接
            {
                std::cout << "(" << ip << ":" << port << ")# closed" << std::endl;
                break;
            }
            else //发生了错误
            {
                std::cout << "read from client err" << std::endl;
                break;
            }
            // std::cout << "size = " << size << std::endl;
        }
    }


    void StartServer()
    {
        char buf[1024] = {0};
        while(1)
        {
            struct sockaddr_in RemoteIp;
            socklen_t len = sizeof(RemoteIp);
            std::cout << "receive from client# ";
            fflush(stdout);

            //两个文件描述符,_sockfd:用来获取远程端口信息;_Remotefd: 给远端提供服务。
            _Remotefd = accept(_sockfd, (struct sockaddr*)&RemoteIp, &len); 
            if(_Remotefd == -1)
            {
                std::cout << "accept failed" << std::endl;
                continue;
            }
           
            std::cout << "sock file descriptor:" << _Remotefd << std::endl;
            std::string ip = inet_ntoa(RemoteIp.sin_addr);
            int port = ntohs(RemoteIp.sin_port);

            // 线程方式接受客户端
            // pthread_t tid;
            // pthread_create(&tid, nullptr,ThreadService, (void*)&_Remotefd );

            // 进程方式接受客户端
            // pid_t pid = fork();
            // if(pid == 0)
            // {
            //     close(_sockfd);
            //     if(fork() > 0)
            //     {
            //         exit(0);  
            		//退出后,产生孤儿进程,将子进程交给操作系统管理;另外对于它的父进程来说,相当于子进程退出,产生了僵尸进程
            //     }
            //     MyService(ip, port);  //父进程执行服务
            //     exit(0);
            // }
            // close(_Remotefd);  // service完成后关闭,防止文件描述符的占用
            // waitpid(pid, nullptr, 0);
        }
    }

    ~tcpserver()
    {
        close(_sockfd);
    }

private:
    short _port;
    int _sockfd;
    int _Remotefd;
    struct sockaddr_in _local;
};

int main()
{
    tcpserver* udpS = new tcpserver;
    udpS->StartServer();
    return 0;
}
  • tcpclient.cpp
#include <iostream>
#include <string>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>

class tcpclient
{
public:
    //填入自己服务器的ip地址
    tcpclient(const std::string &serverIP = "xxx.xxx.xxx.xxx", const int &port = 1666)
        :_serverIP(serverIP)
        ,_serverPort(port)
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(_sockfd == -1 )
        {
            std::cout << "socket create faild" << std::endl;
            exit(-1);
        }
    }

    void Service()
    {
        std::string msg;
        char buf[1024] = {0};
        while(1)
        {
            std::cout << "Send message:" ;
            fflush(stdout);
            std::cin >> msg;

            write( _sockfd, msg.c_str(), msg.size() );

            ssize_t size = read(_sockfd, buf, sizeof(buf) - 1);
            if(size > 0)
            {
                buf[size] = '\0';
                std::cout << "received from server# " << buf << std::endl;
            }
            else
            {
                std::cout << "server closed" << buf << std::endl;
                break;
            }

        }
    }
    void StartClient()
    {
        _RemoteSocket.sin_addr.s_addr = inet_addr(_serverIP.c_str());
        _RemoteSocket.sin_family = AF_INET;
        _RemoteSocket.sin_port = htons(_serverPort);
        memset(&_RemoteSocket.sin_zero, 0, sizeof(_RemoteSocket.sin_zero));

        if(connect(_sockfd, (struct sockaddr*)&_RemoteSocket , sizeof(_RemoteSocket)) == -1)
        {
            std::cout << "connect failed" << std::endl;
            exit(-2);
        }
        else
        {
            // std::cout << "hello wordl" << std::endl;
            Service();
        }

    }

    ~tcpclient()
    {
        close(_sockfd);
    }

private:
    std::string _serverIP;
    int _serverPort;
    int _sockfd; //用来建立连接
    struct sockaddr_in _RemoteSocket; 

};


int main()
{
    tcpclient* tcl = new tcpclient;
    tcl->StartClient();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值