【网络编程】Socket(更新中)

知识汇总:

1.IP地址与端口号

我们知道同一台主机的进程间通信有system V共享内存,消息队列,信号量这些方式,而跨主机的进程间通信怎么搞呢?使用IP地址与端口号!

IP地址用来网络中标识唯一一台主机,是一个32位无符号整数,常常用192.163.1.1这样点分十进制的字符串形式表示。

端口号用来表示一台主机中的一个进程,它是一个16位无符号整数,所以端口号最小是0,最大是65536。那么端口号如何表示一个进程呢?如下图​​​​,端口号作为数组的下标,数组中存放的是进程PID。它相当于一个哈希表,根据下标即端口号就可以找到对应的进程。

【网络编程】Socket(更新中)_第1张图片这里有一个问题,为什么不直接用进程PID呢,非要多走一步端口号,感觉有点多此一举。

我是这样理解的,我们使用的应用程序都是有对应的服务器维护的,我们作为一个客户端需要和服务器进行数据交互,那么就必须明白两个问题,一是服务器在哪,二是与服务器的哪个进程进行通信。当我们通信之前,就必须知道服务器的IP地址与进程PID,那么我们怎么知道呢?IP地址我们可以视为客户端提前知晓且并不变更,那进程PID呢?服务器每重新打开一次进程,PID会一样吗?显然不会,那么我怎么找到服务器的对应进程呢?这里就陷入了一个死循环。

网络:请问您是要和服务器123.123.123.123通信吗?

客户端:对的。

网络:请告诉我你是要和服务器的哪个进程通信呢?

客户端:不知道啊?它的进程每次重新启动,进程号都会变更。

网络:对不起先生,没有进程号我们没法帮您通信。

客户端:我不跟服务器通信我怎么知道服务器的进程号。

当然只有ip地址也是可以接收到数据的,但是交由哪个进程处理,这些数据是什么意思用来干什么的,就成了问题。

为了避免这个问题,就有了端口号的概念。服务器的相应进程会放到一个固定的端口号上,客户端都是提前知晓这个端口号的,所以在通信时,客户端只需要端口号就可以找到对应进程。这也使得许多端口号约定成俗,比如常见的8080端口。

2.主机序列与网络序列

每台计算机的存储顺序不同,分为大端存储和小端存储。大端存储就是低字节放到高地址,小端存储就是高字节放到低地址。如下图,定义一个int num=1;

【网络编程】Socket(更新中)_第2张图片

可以看到01放到高地址处的是大端存储,放到低地址处的是小端存储。 

既然有这种主机存储顺序的不同,那么在进行网络通信时如果两个终端存储顺序不同,那么数据就会被错误解读。为了解决这个问题,就定义了一个共同的标准,在传输网络数据的时候都以大端存储为标准。

 因为客户端发送数据,携带的目的ip与目的端口都是网络序列的,服务器端要对比数据是给哪个端口,所以本地ip和端口必须转为网络序列。

3.多网卡/多IP

这块是关于创建套接字后,使用bind函数绑定端口号与ip的一个细节。

云服务器,或者一款服务器不要bind一个具体的ip,因为服务器可能有多个网卡多个ip地址,这些ip都有可能接收指定端口的数据,所以需要在服务器启动的时候bind任意一个ip地址,这就要求在对sockaddr里面的sin_addr里面的s_addr初始化时,使用INADDR_ANY进行初始化。

接口函数:

socket:

#include          
#include 

int socket(int domain, int type, int protocol);

【网络编程】Socket(更新中)_第3张图片

socket函数用来创建一个套接字。domain选择协议家族来进行通信,ipv4网络通信使用AF_INET,ipv6使用AF_INET6。type是用来选择套接字类型的,  SOCK_STREAM就是面向连接,可靠的,SOCK_DGRAM就是无连接,不可靠的。protocol用0即可,选择默认合适的协议。socket创建成功会返回一个文件描述符,创建失败返回-1。 

bind:

#include          
#include 

int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

【网络编程】Socket(更新中)_第4张图片

bind函数用来绑定本地主机ip与端口号。sockfd就是创建套接字成功返回的文件描述符。

我们可以看到sockaddr是个结构体,那这个结构体的成员有哪些呢?

sockaddr结构体:

【网络编程】Socket(更新中)_第5张图片

__SOCKADDR_COMMON (sa_)就是#define  __SOCKADDR_COMMON(sa_prefix) \

  sa_family_t sa_prefix##family,其实绕来绕去就是sa_family_t  sa_family,一个16位短整型变量(下面sockaddr_in结构体的第一个成员也大体一样,sa_family_t  sin_family,一个16位短整型变量),用来表示地址类型如AF_INET。char sa_data[14]就是14字节的地址数据。【网络编程】Socket(更新中)_第6张图片

不过我们在进行网络通信时,使用的是sockaddr_in类型的结构体

sockaddr_in结构体:

【网络编程】Socket(更新中)_第7张图片

【网络编程】Socket(更新中)_第8张图片

由上图可以看出sockaddr结构体里面的sin_port是一个16位无符号整数,in_addr结构体里面有唯一一个成员---32位无符号整数。他们分别代表一个端口号和IP地址。sin_zero结构体就是填充字段,可以看到用sockaddr结构体大小减去了sockaddr_in结构体里面的三个成员的大小,最后自然sockaddr和sockaddr_in结构体的大小就一样了。这不明摆着是让sockaddr和sockaddr_in适配么。使用时直接取地址然后强转就可以了。

所以得出下面的结论:

IPv4IPv6的地址格式定义在netinet/in.h,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32IP地址.
IPv4IPv6地址类型分别定义为常数AF_INETAF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

【网络编程】Socket(更新中)_第9张图片

addrlen就是一个无符号整形,指明sockaddr结构体大小的。

recvfrom:

#include 
#include 

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

【网络编程】Socket(更新中)_第10张图片

利用创建的套接字把接收到的最大为len字节长度的数据放到buf中,flags标志位表示是否阻塞接收(设为0即可),src_addr指针和addrlen指针分别指向一个输入性参数,用来接收发送方的IP地址端口号以及结构体大小。数据成功则返回实际接收到的字符数,失败返回-1。

sendto:

#include 
#include 


ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

【网络编程】Socket(更新中)_第11张图片

利用创建的套接字发送最大为len字节长度的数据,flags标志位表示是否阻塞发送(设为0即可),dest_addr指针指向一个sockaddr_in结构体(里面有目的ip和目的端口号),addrlen为该结构体大小。成功则返回实际传送出去的字符数,失败返回-1。

inet_addr:

#include 
#include 
#include 

in_addr_t inet_addr(const char *cp);

inet_addr() 函数将互联网主机地址 cp 字符串从 IPv4 数字和点表示法转换为按网络字节顺序的二进制数据。 

inet_ntoa:

#include 
#include 
#include 

     
char *inet_ntoa(struct in_addr in);

inet_ntoa() 函数将按网络字节顺序给出的互联网主机地址转换为 IPv4 点分十进制表示法的字符串。 字符串以静态分配的缓冲区,后续调用将覆盖该缓冲区。不过这里的in_addr是sockaddr_in结构体里面的一个结构体成员,这个in_addr结构体里面存放的是一个32位无符号整数(IP地址)。

htons:

#include 

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

已知端口号是16位无符号整数,ip地址是32位无符号整数。所以这里四个函数就是把主机字节序转换成网络字节序or网络字节序转换成主机字节序,IP地址用uint32_t,端口号用uint16_t。

代码:

代码逻辑:

你可能感兴趣的:(网络)