Linux下套接字创建及连接建立简介 - APUE

20 篇文章 0 订阅
4 篇文章 0 订阅


Unix环境高级编程(APUE)中介绍了套接字socket的使用,本文从开发者使用过程角度简单介绍了服务器开启监听、客户端发起连接、子线程创建的一些过程以及Unix中套接字的地址格式等内容。

连接过程

创建套接字地址

  1. 套接字地址结构
     struct sockaddr_in {
         sa_family_t     sin_family;    /* address family */
         in_port_t       sin_port;      /* port number */
         struct in_addr  sin_addr;      /* IPv4 address */
         unsigned char   sin_zero[8];   /* filler */
     };
    
  2. 创建套接字地址 sockaddr_in (IPv4), sockaddr_in6 (IPv6)
  3. 初始化套接字地址,以IPv4为例
    1. sin_family 表示族,可选IPv4 - AF_INET 或 IPv6 - AF_INET6
    2. sin_port 地址对于的端口,应大于1024,否则需要superuser权限
    3. sin_addr 网络地址的二进制表示,通过预定义的值如本地环回 INADDR_LOOPBACK 、任意 INADDR_ANY 设置,或通过 inet_aton 函数将点分十进制(127.0.0.1)格式转换为所需格式,由于网络字节序与主机字节序可能不同,因此会再使用 htonl, htons, ntohl, ntohs 等函数进行转换。
    4. sin_zero Linux中定义的填充字节,为0
  4. 使用套接字地址 bind, connect

服务器开启监听

创建套接字地址及套接字,绑定,开启监听。其中开启监听在收到请求之前会阻塞(block),请求到来后会返回分配的新套接字描述符。

  1. 创建套接字地址,如上文所述
  2. 创建套接字 socket(int domain, int type, int protocol),返回套接字描述符, -1 on error
    1. domain 指定域,如IPv4 - AF_INET 或 IPv6 - AF_INET6
    2. type 指定连接类型,有如下四种
      1. SOCK_DGRAM 定长、无连接、不可靠报文,默认 UPD
      2. SOCK_RAW IP数据报接口
      3. SOCK_SEQPACKET 定长、有序、可靠、面向连接的报文,需要 AF_UNIX domain
      4. SOCK_STREAM 定长、可靠、双向、面向连接的数据流,默认 TCP
    3. protocol 协议,0为默认,其它可选 IPPROTO_IP, IPPROTO_IPv6, IPPROTO_TCP, IPPROTO_UDP
  3. 绑定套接字到指定地址 bind(int sockfd, const struct sockaddr *addr, socklen_t len),0 OK, -1 on error
    1. sockfd 套接字描述符
    2. addr 套接字地址,需要将 sockaddr_in 类型转换为此类型(转换指针的类型)
    3. len 地址的长度
  4. 开启监听 listen(int sockfd, int backlog),0 OK, -1 on error
    1. sockfd 如前所述
    2. backlog 等待队列中的请求个数

代码

#define SVR_PORT 2300
#define SVR_BACKLOG 10
// listen
void start_listen() {
    // init socket addr
    sockaddr_in svr_addr;
    memset(&svr_addr, 0, sizeof(sockaddr_in));
    svr_addr.sin_family = AF_INET, svr_addr.sin_port = htons(SVR_PORT), svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // init socket to listen
    int sd_li = socket(AF_INET, SOCK_STREAM, 0);
    if (sd_li == -1)
        err_exit(-1, "socket: create socket failed.");

    // bind socket to addr
    int err = bind(sd_li, (sockaddr *)&svr_addr, sizeof(svr_addr));
    if (err != 0)
        err_exit(err, "bind: bind failed.");

    // listen
    err = listen(sd_li, SVR_BACKLOG);
    if (err == -1)
        err_exit(err, "listen: listen failed.");
    printf("start listen on port %d\n", SVR_PORT);

    // accept client request
    while (1) {
        sockaddr_in addr_peer;
        socklen_t len_peer;
        int sd_acc = accept(sd_li, (sockaddr *)&addr_peer, &len_peer);
        if (sd_acc == -1)
            err_exit(sd_acc, "accept: accept client error.");
        printf("connected to client %xd.\n", addr_peer.sin_addr.s_addr);

        // start a new thread and send hello back
        pthread_t subthread;
        err = pthread_create(&subthread, NULL, client_thread_fn, (void *)sd_acc);
        if (err != 0)
            err_exit(err, "pthread_create: create sub thread error.");
    }
}

客户端连接建立

  1. 创建套接字地址及套接字,如上文所述
  2. 连接 connect(int sockfd, const struct sockaddr *addr, socklen_t len), 0 if OK, -1 on eror
    1. sockfd 如前所述
    2. addr 套接字地址
    3. len 地址长度
  3. 关闭套接字 close,该方法实际上是关闭了套接字(文件)描述符,因此需要包含 unistd.h 头文件

代码

#define SVR_HOST_STR "127.0.0.1"
// connect to server, return socket descriptor if success
int connect() {
    // init socket
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET, addr.sin_port = htons(SVR_PORT);
    inet_aton(SVR_HOST_STR, &addr.sin_addr);

    // create socket
    int sd = socket(AF_INET, SOCK_STREAM, 0);
    if (sd == -1)
        err_exit(sd, "socket: create socket failed.");

    // connect
    int ret_conn = connect(sd, (sockaddr *)&addr, sizeof(addr));
    if (ret_conn != 0)
        err_exit(ret_conn, "connect: connect failed.");
    return sd;
}

套接字地址格式

由于不同的domain地址格式不同,因此Unix定义了统一的格式 sockaddr 用来内部使用,而开发者则针对不同的domain使用特定的格式,如 sockaddr_insockaddr_in6,使用时需要将这些domain特定的格式转换为 sockaddr 格式。以下为Linux下这些格式的定义:

struct sockaddr {  
    sa_family_t   sa_family;  /* address family */  
    char          sa_data[];  /* variable-length address */ 
};
struct sockaddr_in {
  sa_family_t     sin_family;    /* address family */
  in_port_t       sin_port;      /* port number */
  struct in_addr  sin_addr;      /* IPv4 address */
  unsigned char   sin_zero[8];   /* filler */
};
struct in_addr {
  in_addr_t       s_addr;        /* IPv4 address */
};

上述 sockaddr_in 格式中的 in_addr 表示因特网地址(二级制或者说整数形式,不同于点分十进制 127.0.0.1)。当然,Unix提供了 inet_ntopinet_pton 两个函数,实现点分十进制与二进制之间的转换。前者将二进制转换为点分十进制,后者将点分十进制转换为二进制。其中点分十进制为 char *restrict str 字符串,size 指字符串缓冲区的长度;二进制为 void *restrict addr

const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);
int inet_pton(int domain, const char *restrict str, void *restrict addr);

如前所述,由于网络字节序Byte Order与主机字节序可能不同,因此Unix提供了下列四个函数用来转换,其中h表示主机hostn表示networkls分别表示longshort

uint32_t htonl(uint32_t hostint32);
Returns: 32-bit integer in network byte order
uint16_t htons(uint16_t hostint16);
Returns: 16-bit integer in network byte order
uint32_t ntohl(uint32_t netint32);
Returns: 32-bit integer in host byte order
uint16_t ntohs(uint16_t netint16);
Returns: 16-bit integer in host byte order

数据传输

数据传输过程主要为发送和接收数据两种,两种各包含四个相似函数可以使用,这里主要介绍其中第一种,即 sendrecv ,如下:

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

前三个参数不难理解,分别是套接字描述符、待发送/接收消息缓冲区,缓冲区长度,最后一个是标志。一般为0即可,其它值如 MSG_OOB, MSG_PEEK 等表示不同含义,此处不一一列出。

线程创建

线程创建主要使用 pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg); 函数,注意,g++需要在编译时加上参数 -lpthread 才能使用相关函数。 attr 为属性,一般设置为 NULL 即可,start_routine 函数为子线程的开始函数,arg为其参数。子线程创建后会从该函数开始执行。

代码

// thread function
void *thr_fn(void *x) {
    //...
}
int main{
    int sd = 1; // para
    pthread_t sub_thr;
    int err = pthread_create(&sub_thr, NULL, thr_fn, (void *)sd);
    if (err != 0)
        err_exit(err, "create sub thread failed.");
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值