网络编程就是通过网络协议实现计算机间的通信。那么就有两个问题,如何精准的定位主机和如何进行高效的数据传输。
IP地址来标识网络中的主机,端口号用来定位主机中的某个进程。通过IP和端口号可以唯一确定网络的某台计算机中的进程。
套接字
即socket,用于描述IP地址和端口,是一个通信的句柄,进程利用套接字向网络发起请求或回复请求。
套接字的工作原理
要实现网络间的通信,至少需要一对套接字,一个运行于客户端,一个运行于服务端。套接字的连接过程可以分为,
1 服务端监听 2 客户端连接请求 3 连接请求确认
服务端监听:服务端并不定位到具体的客户端套接字,而是处于等待连接状态,实时监听网络的状态
连接请求:即客户端套接字向服务端发起连接请求,目标是服务端的套接字,为此客户端要描述服务端的地址和端口号,然后想服务端套接字发起连接请求。
连接确认;即服务端接收到客户端的连接请求之后,建立一个新的线程与客户端套接字建立连接,并向客户端回复服务端的地址和端口信息,客户端确认了此连接,该连接就建立好了,而服务端继续处于监听状态,监听其他套接字发起的请求。
struct in_addr {
in_addr_t s_addr; // 32-bit IPv4 address
//network byte ordered
}
struct sockaddr_in {
sa_family_t sin_family; //AF_INET
in_port_t sin_port; //16-bit TCP or UDP port nummber, network byte ordered
struct in_addr sin_addr; //32-bit IPv4 address, network byte ordered
char sin_zero[8]; //unused
}
sockaddr_in是网络套接字地址结构,大小为16字节,定义在
struct sockaddr {
sa_family_t sa_family; //address family: AF_XXX value
char sa_data[14]; //protocol-specific address
}
sockaddr是通过套接字地址结构,当作为参数传递给套接字函数时,套接字地址结构总是以指针方式来使用,比如bind/accept/connect函数等
网络字节序转换在此不多做介绍了。
地址转换函数:inet_ntop(); inet_pton();
#include
int inet_pton(int family, const char *strptr, void *addrptr); // 返回:成功为1,输入不是有效表达式返回0,出错为-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); // 返回:成功为指向结果的指针,出错为NULL
这两个函数对于IPv4和IPv6都适用,p代表表达式(presentation)、n表示数值(numeric)。第一个函数尝试转化由strptr指针所指的字符串,通过addptr指针存放二进制结果,成功返回1,如果对指定的family而言输入的不是有效的表达格式,那么返回0
inet_ntop进行相反的操作,如果len的值太小,不足以存放表达式结果,则返回一个空指针,并置error为ENOSPC。inet_ntop函数的strptr参数不可以是一个空指针,调用者必须为目标存储单元分配内存并制定其大小,调用成功时,这个指针就是该函数返回值。
SOCKET函数 int socket(int domain, int type, int protocol);
为了执行网络IO,第一件事就是调用SOCKET,创建套接字描述符,指定期望的通信协议族(比如IPv4、IPv6)和套接字字类型(字节流、数据报或原始套接字)。
family的值有:
type的值有:
protocol的值有:
返回的socket描述字,标识套接字。返回值存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。所以需要由bind来确定该套接字所对应的网络地址和端口,用来和服务器通信。
BIND函数
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); 成功返回零,失败返回-1;
bind即把协议地址和端口号赋予一个套接字,往往服务端需要bind,bind后,即可在bind的端口上监听服务请求,客户端往往不需要bind。
为什么服务端需要Bind,客户端可以不bind呢?
因为服务端始终是网络连接中的被动方,需要在一个众所周知的端口上等待连接请求,而且作为服务弃端口应该是固定的,所以需要bind固定端口提供服务。而客户端是发起方,我们并不关心是哪个端口与服务器建立了连接,内核会自动分配一个无冲突的端口号,我们自己bind的话,还有可能端口冲突。当需要指定的端口通信的时候,才需要bind。