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套接字编程的基础,下一篇文章,将会运用本章所介绍的函数,编写一个客户服务端程序.