本篇介绍的是Linux下的网络编程,故有些接口是不适用于Windows的,但是具体概念和实现方法是大体一致的
本篇重在讲解原理,具体实现请戳这里->UDP套接字编程实现
socket相当于是一个文件描述符,我们将数据写入socket中,再发到目标主机,目标主机接收socket,再从socket中读取数据,至此便实现了网络中两台主机间的数据传输,即进程通信。
举个栗子:如果是两个人进行通信的话,首先要知道彼此的电话号码,而且要明确两个人谈论的事项,即使是闲聊也算生活事项的一种。网络中的两台主机进行通信也是如此,电话号码即彼此的ip地址,谈论的事项即彼此主机上确定的一个端口号
网络编程套接字可以看做是ip地址+端口号,ip地址为了明确网络中唯一一台主机,而端口号确定了这台主机上的唯一一个进程,因为一个端口号只能被一个进程占用,而且只有网络进程才有端口号。
IP协议有两个版本,IPv4和ipv6,现在普遍用的都是IPv4,IPv6是为了解决现有的IP地址可能不够的情况,但还没有普及开来。我们接下来使用的全是IPv4协议。
端口号是传输层协议的内容
大家可能刚看到这个觉得有点陌生,我来为大家解释一下。
我们都知道我们的计算机存储数据时是分大端和小端的,而且主机和主机之间的存储模式是不固定的,也就是没有统一的标准,即如果你是大端的模式,而对方却是小端的模式,你们在交换数据时就会发生错误,所以为了避免差异化,TCP/IP协议规定,网络数据流应采用大端字节序,即低地址存放数据的高字节。
无论当前主机是大端机还是小端机,是发送端还是接收端,都要按照这个TCP/IP协议规定的网络字节序来处理数据,如果当前发送主机是小端。则需要先将数据转成大端;否则就忽略,直接发送即可;
调用以下库函数可以实现网络字节序和主机字节序之间的转换
#include
uint32_t htonl(uint32_t hostlong);
//将32位的长整数从主机字节序转换位网络字节序
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
socket常用API
//创建 socket 文件描述符(TCP/UDP, 客户端 + 服务器)
//domain: 一个地址描述。目前仅支持AF_INET,,代表sockaddr_in结构体
//type: 服务类型,UDP为SOCK_DGRAM, TCP为SOCK_STREAM
//protocol: 套接口所用的协议。如调用者不想指定,可用0指定,表示缺省。
int socket (int domain, int type, int protocol);
//绑定端口号(TCP/UDP, 服务器)
//客户端不用该接口,而且对于服务器而言,端口号必须是众所周知,不可修改的
int band (int socket, const struct sockaddr *address, socklen_t address_len);
//开始监听 socket(TCP, 服务器)
//作用:将套接字由新创建状态设置为监听状态,即监听是否有客户端请求连接
//backlog: 监听队列,不要设置的太大,5~10就差不多了,避免浪费资源
int listen(int socket, int backlog);
//接受请求(TCP, 服务器)
//与客户端连接成功后,返回一个用于服务的文件描述符,也可以当做是客户端的文件描述符,用于写入和读取数据
//否则进行阻塞式等待,失败返回-1
int accept (int socket, struct sockaddr* address, socklen_t* address_len);
//建立连接(TCP, 客户端)
int connect (int socket,const struct sockaddr* address, socklen_t addrlen);
sockaddr结构
socket常见API中经常出现sockaddr*
,下面就为大家详解该结构体的作用以及用法 .
sockaddr用于存储参与套接字通信的一个计算机上的IP协议地址。为了统一地址结构的表示方法 ,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。
但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in。实际填充字段的每一部分则遵循sockaddr_in数据结构,两者大小都是16字节,所以二者之间可以进行切换。
struct sockaddr {
unsigned short sa_family; //:是2字节的地址家族,一般都是“AF_xxx”的形式,通常用AF_INET
char sa_data[14]; //14字节的地址数据
}
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
//sin_family:指代协议族,在socket编程中只能是AF_INET
//sin_port:存储端口号(使用网络字节顺序)
//sin_addr:存储IP地址,使用in_addr这个数据结构
//sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
typedef struct in_addr {
union {
struct{ unsigned char s_b1,s_b2, s_b3,s_b4;} S_un_b;
struct{ unsigned short s_w1, s_w2;} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR;
阐述下in_addr的含义,很显然它是一个存储ip地址的共用体有三种表达方式:
第一种用四个字节来表示IP地址的四个数字;
第二种用两个双字节来表示IP地址;
第三种用一个长整型来表示IP地址。
给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如addrto.sin_addr.s_addr=inet_addr(“192.168.0.2”);
其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。