Osheep

时光不回头,当下最重要。

socket网络编程之一(TCP套接字API)

socket是对TCP/IP协议族的封装,并对网络编程开发人员提供可用的接口,可以说,任何网络编程都离不开socket,要想熟练运用网络编程技术,必须掌握socketAPI.本篇文章便是对socketAPI的介绍.

套接字地址结构

IPV4套接字地址结构

typedef __uint32_t  in_addr_t;

struct in_addr {
    in_addr_t s_addr;     /* 32位的IP地址 */
};                                      /* 网络子节序 */

struct sockaddr_in {
    __uint8_t   sin_len;      /* 结构体的长度 */
    sa_family_t sin_family;      /* AF_INET协议族 */
    in_port_t   sin_port;               /* 16位的端口号,网络子节序 */
    struct  in_addr sin_addr; /* 32位IP地址 */
    char        sin_zero[8];  /* unused */
};
  • 长度字段sin_len表示struct sockaddr_in的长度,在使用的时候,并不是必须显示的对该字段赋值,它是一个无符号8位整形,即unsigned char类型
  • sin_family是一个_uint8_t类型,表示套接字地址结构的协议族,比如AF_INET表示internet协议族。
  • sin_port 是一个_unint16_t十六位无符号整形,表示TCP协议中的端口号, 是网络子节序。
  • sin_addr 是一个32位无符号整形的IP地址,将它设置成结构体类型,是为了考虑内存对齐.
  • sin_zero字段,也不需要显示设置,基本不用.

通用套接字地址结构

strict sockaddr{
     __uint8_t ss_len;
    sa_family_t  sa_family;
    char sa_data[14];
}

当套接字地址结构作为一个参数传递进任何套接字函数时,套接字地址结构总是以指针的形式来传递,然而以这样的指针作为参数之一的任何套接字函数,必须处理来自任何协议族的套接字地址结构。因此ANSIC便创造了通用套接字地址结构,当时候IPV4套接字地址结构传递时,需要进行强制转换.

套接字函数

socket函数

#inclucde<sys/socket.h>
int socket(int family,int type,int protocol);
  • socket函数在成功时,返回一个非负整数,称之为套接字描述符,失败时返回一个负数,错误信息在error中.
  • family表示协议族,比如AF_INET表示IPV4协议,AF_INET6表示IPV6协议,AF_LOCAL表示Unix表示域协议等.
  • type表示套接字类型,SOCK_STREAM表示TCP套接字,SOCK_DGRAM表示数据报套接字(UDP).protocol一般设置为0表示family和type组合的系统默认值.

connection函数

#include<sys/socket.h>
int connection(int sockfd,const struct *sockaddr,socklen_t addrlen);
  • sockfd是一个套接字描述符,由socket函数指定,第二个和第三个参数分别指定套接字结构和套接字的长度.sockaddr必须包含服务端的IP地址和端口号.
  • TCP客户端用connection函数来建立与TCP服务端的连接,因此connection函数是一个客户端使用的函数,如果是TCP套接字,调用connection函数将激发TCP的三次握手过程,只有当建立成功或者是失败时该函数才返回,因此,在连接过程中,程序时阻塞的.
  • 客户程序调用connection内核会确定客户端的IP地址,并且会分配一个临时端口号.作为源端口.
  • 错误信息有以下几种情况
    1.若TCP客户没有收到SYN分节的响应,则会返回ETIMEDOUT错误,举例来说,4.4BSD内核发送一个SYN,如果没有响应,则等待6s再发送一个,如果还没有响应,则等待24秒发送一个,若总共等待了75秒还没有响应则返回ETIMEOUT错误.
    2.若对客户的SYN的响应是RST(复位),则表明该服务器主机,在指定的端口没有进程,等待连接(比如服务器进程未运行),这是一种硬错误,客户一接收到RST则立马返回ECONNREFUSED.
    3.若客户发出的SYN在中间的某个路由器上引发了一个destination unreachable的ICMP错误,该错误会保存在内核中,然后会按照第一种情况来处理,如果,75秒还是没有响应,则返回EHOSTUNREACH
  • 当connection函数返回失败后必须关闭当前套接字描述符,并重新调用socket,然后重新连接.

bind函数

#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,sickle_t addrlen);
  • bind函数通常用于设置本机的地址和端口号,对于客户端来说,不一定必须调用该函数,如果不调用该函数,客户端在和服务端通信的时候,会读取本机的地址和设置一个临时的端口号,和服务器通信。
  • 服务端需要调用该函数,本机的地址通常设置为INADDR_ANY,表示内核自己设置本机IP,通常要明确设置端口号,不设置端口号是很罕见的。bind函数返回的一个常见的错误是:EADDRINUSE(端口地址被占用).

listen函数

#include<sys/socket.h>
int listen(ins sockfd,int backlog);
  • listen函数仅由TCP服务器调用,当socket函数创建一个套接字描述符时,它被假设为一个主动套接字,即它将调用connect函数,listen函数把一个未连接的套接字,转换成一个被动套接字,它指示内核应该接受连接请求,到接字状态从CLOSED状态转换成LISTEN状态.
  • backlog规定了内核应为相应套接字排队的最大连接数.
  • 内核为任何一个给定的监听套接字维护了两个队列:
    (1)未完成连接队列,当客户程序,发送请求连接(第一次握手)到达时,内核会将这个过程放入该队列
    (2)已完成连接队列,当客户程序完成了三次握手时,内核会将未完成队列移到已完成队列中去.以上的处理,是内核处理,服务器的进程无需插手.当服务器进程调用accept时,内核会将已完成队列的对头的信息,返回给进程,如果该队列为空,进程会进入睡眠(阻塞),直到有队列不为空才返回.
  • backlog表示未连接队列和已连接队列之和的最大值,注意!不要将backlog设置为0.

accept函数

#include<sys/socket.h>
int accept(int sockfd,struct sockaddr * clientaddr,socklen_t *addrlen);
  • accept由服务器调用,如果accept返回成功,其返回值是由内核自动生成的一个全新描述符,代表与客户端的TCP连接(此时连接已经完成了,可以进行数据交互了),比如我们要发送数据,则调用write(fd,buffer,strlen(buffer));此处的fd便是由accept返回的,他就是一个连接区分TCP连接的标记(不然服务器怎么知道把数据返回给哪个socket连接).
  • 此函数最多返回三个值,一个是函数的返回值,clidetaddr是一个指针类型的,因此传一个地址过去,clientaddr便会返回客户端的套接字地址结构信息,比如客户端的ip地址和端口号,addrlen表示客户端套接字地址结构的长度,如果对客户端的地址结构不敢兴趣,第二个参数和第三个参数可以传NULL.

总结

本文对tcp套接字的API的各个函数进行了介绍,上面介绍的函数是理解TCP套接字编程的基础,下一篇文章,将会运用本章所介绍的函数,编写一个客户服务端程序.

点赞