Linux下socket编程基础1-初探

Linux下socket套接字基础


int socket(int __domain, int __type, int __protocol);

  • domain:是协议域,这个参数决定了socket的地址类型,常用的是AF_INET(ipv4地址)和16位端口号组合
  • type:是指定socket类型,常用的socket类型,SOCK_STREAM(基于TCP),SOCK_DGRAM(基于UDP)
  • protocol: 指定协议,常用IPPROTO_TCP、IPPTOTO_UDP等

调用socket()返回的描述字存在于协议族空间中,但是没有一个具体的地址,所以需要调用下面的bind()函数设置具体地址,否则当调用connect()、listen()时这个端口系统会随机分配。

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

  • sockfd:就是socket的描述字,socket()的返回值
  • *addr :
    一个指向sockaddr结构体的指针,指向要绑定给sockfd的协议地址,这个结构体随着地址结构的改变而调用不同的结构体
  1. ipv4:
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
  1. ipv6:
struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};
  • addlen: 对应的是地址的长度

int listen(int sockfd, int backlog);

服务端通过listen()将socket转变为被动类型,等待客户的连接请求

  • sockfd:     第一个参数即为要监听的socket描述字.
  • backlog:   第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

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

客户端通过调用connect()来建立于服务器的连接

  • sockfd: 第一个参数是客户端的socket描述字
  • *addr : 第二个参数是服务器的socket地址
  • addrlen :第三个参数是服务器socket地址的长度

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

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

  • sockfd : 服务器socket描述字
  • *addr : 返回客户端的socket地址
  • *addrlen : 客户端的socket地址长度
  • 返回值: 已连接的socket描述字

注意:服务器通常只需要创建一个socket套接字,这个套接字在服务器生命周期内一直存在。而客户端连接成功服务端后,内核会创建一个新的已连接的socket套接字,当服务器对此客户端完成服务之后,这个socket描述字就会被关闭。


recvmsg()和sendmsg()函数(最通用的I/O函数)

用这两个函数可替代

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvfrom()/sendto()
#include <sys/socket.h>
#include <sys/uio.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr {
    void          *msg_name;        // protocol address
    socklen_t      msg_namelen;     // size of protocol address
    struct iovec  *msg_iov;         // scatter/gather array
    int            msg_iovlen;      // elements in msg_iov
    void          *msg_control;     // ancillary data (cmsghdr struct)
    socklen_t      msg_controllen;  // length of ancillary data
    int            msg_flags;       // flags returned by recvmsg()
};

struct iovec {//依赖于头文件sys/uio.h
    void    *iov_base;      /* starting address of buffer */
    size_t  iov_len;        /* size of buffer */
}
  • msg_name和msg_namelen
    这两个成员用于套接字未连接的场合(如未连接 UDP 套接字)。
    如果用于TCP的话,msg_name置为空指针,namelen置为0。

  • msg_iov和msg_iovlen
    这两个成员指定输入输出缓冲区数组以及数组的多少,结合struct iovec来看,msg_iov是个二维数组

  • msg_control和msg_controllen
    这两个成员指定可选的辅助数据的位置和大小

  • msg_flags
    对于sendmsg,这个参数可以忽略掉。
    对于recvmsg,它本身的参数flags会被复制到msg_flags成员,由内核使用这个值驱动接收处理过程。

recvmsg 返回的 7 个标志如下:

  • MSG_BCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据包作为链路层广播收取或者其目的 IP 地址是一个广播地址。与 IP_RECVD-STADDR 套接字选项相比,本标志是用于判定一个 UPD 数据包是否发往某个广播地址的更好方法。
  • MSG_MCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据报作为链路层多播收取。
  • MSG_TRUNC:本标志的返回条件是本数据报被截断,也就是说,内核预备返回的数据超过进程事先分配的空间(所有 iov_len 成员之和)。
  • MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断,也就是说,内核预备返回的辅助数据超过进程事先分配的空间(msg_controllen)。
  • MSG_EOR:本标志的返回条件是返回数据结束一个逻辑记录。TCP 不使用本标志,因为它是一个字节流协议。
  • MSG_OOB:本标志绝不为 TCP 带外数据返回。它用于其他协议族(如 OSI 协议族)。
  • MSG_NOTIFICATION:本标志由 SCTP 接收者返回,指示读入的消息是一个事先通知,而不是数据消息。
示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>

using namespace std;

void My_server(void){
    //初始化发送数据缓冲区
    char buf[128]={0};
    struct iovec buffer={
        .iov_base = buf,
        .iov_len = sizeof(buf)
    };
    struct msghdr Mymsg={
        .msg_name = nullptr,
        .msg_namelen =0,
        .msg_iov = &buffer,
        .msg_iovlen = 1,
        .msg_control = nullptr,
        .msg_controllen = 0,
        .msg_flags = 0
    };
    //创建套接字
    int Mysocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    //将套接字和IP、端口绑定
    struct sockaddr_in Server_Addr;
    memset(&Server_Addr,0,sizeof(Server_Addr));
    Server_Addr.sin_family=AF_INET; //设置使用ipv4地址
    Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");//设置具体的ip地址(本地环回)
    Server_Addr.sin_port=htons(12345); //端口
    bind(Mysocket,(struct sockaddr *)&Server_Addr,sizeof(Server_Addr));//应用 地址和端口的设置

    //进入监听状态
    listen(Mysocket,20);
    //接收客户端请求
    struct sockaddr_in Client_Addr;
    socklen_t client_addr_size = sizeof(Client_Addr);
    std::cout << "ready accept"<<endl;
    int client_sock = accept(Mysocket,(struct sockaddr *)&Client_Addr,&client_addr_size);//在此阻塞,等待客户端请求
    std::cout << "received message"<<endl;
    //向客户端发送数据
    char str[] = "hello,world!";
    Mymsg.msg_iov->iov_base = str;

    sendmsg(client_sock,&Mymsg,0); //write(client_sock,str,sizeof(str));用write也是可以的

    close(client_sock);
    close(Mysocket);
}

void My_client(void){
    char buf[128] = {0};
    struct iovec buffer={
        .iov_base = buf,
        .iov_len = sizeof(buf)
    };
    struct msghdr Mymsg={
        .msg_name = nullptr,
        .msg_namelen = 0,
        .msg_iov = &buffer,
        .msg_iovlen = 1,
        .msg_control = nullptr,
        .msg_controllen = 0,
        .msg_flags = 0
    };

    int Mysocket = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in Server_Addr;
    memset(&Server_Addr,0,sizeof(Server_Addr));
    Server_Addr.sin_family=AF_INET;
    Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    Server_Addr.sin_port=htons(12345);
    connect(Mysocket,(struct sockaddr*)&Server_Addr,sizeof(Server_Addr));

    std::cout << "ready accept message"<<endl;
    recvmsg(Mysocket,&Mymsg,0); //接收信息
    printf("%s\n",Mymsg.msg_iov->iov_base);
    std::cout << "read message done"<<endl;

    close(Mysocket);
}

int main(int argc, char **argv){
    string select;
_Start:
    std::cout << "Select Mode Server or Client:(c/s)";
    cin >> select;
    std::cout << "select:" << select <<endl;
    if(!select.compare("s")){
        My_server();
    }else if(!select.compare("c")){
        My_client();
    }else{
        std::cout << "please input \"server\" or \"client\""<<endl;
        std::cout << "Please re - enter parameters at boot time"<<endl;
        goto _Start;
    }
    return 0;
}

/*
    创建套接字: socket()
    绑定本机端口: bind()
    建立连接: connect(),accept()
    侦听端口: listen()
    数据传输: send(), recv()
    输入/输出多路复用: select()
    关闭套接字:   closesocket()
*/

Makefile太麻烦了,利用cmake编译

/***CMakeLists.txt***/
cmake_minimum_required(VERSION 3.10.2)

PROJECT(mysocket)

add_executable(Mysocket Mysocket.cpp)
posted @ 2020-09-15 14:21  JoyooO  阅读(144)  评论(0编辑  收藏  举报