源代码在github上点击这里
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
套接字:IP地址 + TCP端口号
(socket) 一个套接字,唯一标识网络通讯中的一个进程
TCP/IP协议中,两个进程若想建立连接,就离不开套接字,它是TCP连接中的端点
套接字地址结构:(用来保存套接字的相应信息)
linux下
IPv4套接字地址结构
IPv4套接字地址结构通常也叫做“网际套接字地址结构”,以sockaddr_in命名:定义在<netinet/in.h>
struct in_addr
{
in_addr_t s_addr; /*32位IP地址*/
};
struct sockaddr_in
{
short sin_family; /*Address family一般来说AF_INET(地址
族)PF_INET(协议族)*/
unsigned short sin_port; /*Port number(必须要采用网络数据格式,普
通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr; /*IP address in network byte
order(Internet address)*/
unsigned char sin_zero[8]; /*Same size as struct sockaddr没有实
际意义,只是为了 跟SOCKADDR结构在内存中
对齐,是为了让sockaddr与sockaddr_in
两个数据结构保持大小相同而保留的空字节。*/
};
通用套接字地址结构:不建立该类型的结构体变量,而只是在传参的时候,进行强制类型转换成该类型,
和void*类型一样,为了函数支持各种类型的数据
struct sockaddr
{
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
AF_前缀代表地址族,PF_前缀代表协议族
sa_family是地址家族,一般都是“AF_xxx”的形式。好像通常大多用的是都是AF_INET。
sa_data是14字节协议地址。
此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构,如sockaddr_in,sockaddr_un,sockaddr_in6...
套接字地址结构体使用方式 :
IPv4地址用sockaddr_in结构体表示,IPv6地址用sockaddr_in6结构体表示,UNIX Domain
Socket地址用sockaddr_un结构体表示,为了让socket API可以接受各种类型的sockaddr结构体
指针做参数,例如bind,accept,connect等函数,这些函数的参数应该设置为void* 类型的指
针,但socket API的实现早于ANSI C标准化,那个时候还没有void* 类型,因此这些函数的参数
都用struct sockaddr* 类型表示,所以struct sockaddr*是一个通用类型指针。在传递参数之前
要强制类型转换:如
struct sockaddr_in servaddr;
bind(sock_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));
建立TCP连接所用函数解析:
1.socket();
函数原型: int socket(int domain,int type,int protocol);
作用 :socket函数是一种可用于根据指定的地址族、数据类型和协议来分配一个套接口的描述
字及其所用的资源的函数
参数:
domain: 一个地址描述。目前仅支持AF_xxxx格式:AF_INET代表IPv4地址
type: 新套接字的类型描述:SOCK_STREAM : 提供面向连接的稳定数据传
输,即TCP协议。 SOCK_dGRAM:表示面向数据报的传输协议,即UDP协议
protocol : 套接字所用的协议。如调用者不想指定,可用0指定,表示缺省。
返回值:
成功返回文件描述符>0,失败返回<0,
2.bind()
函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用: 绑定服务器ip地址和端口,因为服务器程序所监听的网络地址和端口号通常是固定不变
的,将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描
述的地址和端口号
参数:
sockfd:socket创建成功的返回值
addr :存有套接字信息的结构体的地址(注意强制类型转换)
addrlen: 前一个套接字结构体类型的变量的长度:addr参数可以接受多种协议的
sockaddr结构体,而它们的长度各不相同,所以需要第三个addrlen指定结构体的长度。
返回值:
成功返回0,失败返回-1
3. listen():
函数原型:int listen(int sockfd,int backlog);
作用:仅被TCP服务器调用,声明sockfd处于监听状态,并且最多有backlog个客户端处于
连接等待状态,
返回值:成功返回0,失败返回-1;
4.accept():
函数原型:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
作用:用于接收客户端的连接请求
参数:
sockfd:称为监听套接字,即服务器的套接字,也是其文件描述符
addr : 客户端的套接字地址结构体的变量的地址
addrlen:传入传出型参数,传入的是调用者提供的缓冲区的长度,以避免缓
冲区溢出,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提
供的缓冲区)
返回值:
成功返回客户端的IP地址和端口号(即套接字),失败返回-1
5.网络字节序和主机字节序的转换:(为使网络程序具有可移植性,使同样的代码在大端小端机器都可
编译正常运行,对应于开篇的首句话)
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数(IP地址),s表示16为短整数(端口号);
例如:htonl表示将32位的长整数从主机字节序转换为网络字节序,即:将IP地址转换后准备发
送,若主机是小端字节序,这些函数将参数做相应的大小端转换后返回,若是大端字节序,这些函
数不做转换,将参数原封不动的返回
6.字符串转in_addr的函数:
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family,const char* strptr,void *addrptr);
7.in_addr转字符串的函数:
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family,const void* addrptr,char* strptr,size_t len);
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因
此函数接口是void *addrptr
8. connect():
函数原型:int connect (int sockfd,const struct sockaddr *addr,socklen_t addrlen)
作用:客户端调用connect连接服务器
参数:其参数形式和bind一致,区别在于bind的参数是自己的地址,而connect参数是对方的地址。
返回值:
成功返回0,失败返回-1.