网路是怎样连接的(五)Socket API

思考重点

如何将应用程序消息委託给协议栈发送?socket是调用那些函式进行收发操作?

核心知识

协议栈如何进行收发操作

现在将拥有的数据整理一下,首先HTTP消息封包已经由应用程式打包完成,服务器IP地址也已经透过DNS[1]请求机制获得。在两个前提条件都满足的状况下,我们就可以着手思考要怎么将这些数据发给对方服务器的应用程式

发送数据其实是调用多个socket库函式达成的,藉由委託多个函式API进行一连串的任务交互,每个任务完成的项目不同,有建立连接部分、断开连线等等,这些操作的用意就是为了保证双方是否接收到消息与回应是否正常[2]

使用socket实现

为了使双方应用程式之间建立一条专属的沟通管道,我们调用了socket库函式,很多书上将建立socket形容成搭建一条无形的通道,双方可透过这条通道来实现消息的收发操作,不过并不是说建立socket后计算机才被允许与网路进行通讯,其实早在建立socket之前计算机就可以向网路收发消息了,建立socket比较像是彼此确定我们该走哪一条传输通道找到对方的应用程式

顺着这个思路这小节将介绍应用程式是如何调用socket库函式向下层委託收发。我喜欢用一个网路订房的比喻来形容建立socket连线的步骤,就像我们是使用手机app进行订房,委託系统进行操作,真正的流程实际上是不得而知的,这就像站在应用层的视角看待socket连线[3]

创建阶段

目的: 依照指定类型创建socket

就下载这个订房app吧!

程式案例

我们先来看看socket create部分:

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

domain决定socket在网路传输中要使用哪个通讯协定的家族系列AF_INET为TCP/IP网路通讯协定type指定socket的类型SOCK_STREAM对应的是TCP协定SOCK_DGRAM对应的是UDP协定SOCK_RAW可以是IP或ICMPprotocol通常在设定完domain与type以后通讯种类就大抵完成了,因此 protocol 一般都设为0,表示依照指定类型设定预设协议
socket(AF_INET, SOCK_STREAM, 0); // 选择 TCP socket(AF_INET, SOCK_STREAM, 6); // 还是 TCP socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 依然是 TCP socket(AF_INET, SOCK_DGRAM, 0); // 这次是 UDP
#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <sys/socket.h>// #define AF_INET 2// #define SOCK_STREAM 1int main(int argc, char *argv[]){   uint32_t socket_identifier = 0; /*创建socket*/ socket_identifier = socket(AF_INET , SOCK_STREAM , 0);   switch(socket_identifier){ case 1: // 正数 case 2: ... case n: printf("socket create successfully!\n"); break; case -1: printf("socket create error!\n"); }   return 0;  }

socket创建成功一般都会回传一个大于0的标示符,若回传负数则代表socket创建异常

连线阶段

目的: 使两个应用程式建立连线通道

就用这个帐号登入app吧

程式案例

来看看连现阶段吧:

int connect(int fd, struct sockaddr *server, int addrlen);

connect函数是客户端发起的请求,目的是为了与服务器建立连线,介绍参数前,先来讲讲sockaddr这个结构体,它里面装的主要就是socket连线需要的消息,这里暂且以IPv4为例:

struct in_addr{        ip_addr_t s_addr;}; struct sockaddr{     unsigned char  sin_family; //AF_INET所以是IPv4     unsigned short sin_port; // 应用程式端口号     struct in_addr sin_addr; // 服务器IP地址     unsigned char  sin_zero[8]; // 不会用到};
fdsocket的描述符其实就是上面创建socket的回传值标示符socket_identifierserversockaddr结构体,负责提供socket所有的连线消息addrlen结构体sockaddr长度
#include <stdio.h>  #include <stdlib.h>  #include <stdint.h>  #include <string.h>  #include <malloc.h>#include <sys/types.h>   #include <sys/socket.h>  #include <netinet/in.h>#define SERV_PORT 8080typedef sockaddr* info;int main(argc, char* argv[]){info serv=(info)calloc(0, sizeof(sockaddr));uint8_t resp;/*创建socket部分*//*填入socket消息*/serv->sin_family = AF_INET;serv->sin_port = htons(SERV_PORT);inet_pton(AF_INET, "127.0.0.1", serv->sin_addr); resp = connect(socket_identifier, (info)serv, sizeof(sockaddr));      if(resp < 0)        printf("socket connect error!\n");  return 0;  }

藉由创建socket函式API,我们取得本地端的socket编号socket_identifier[4],接下来的任务就是要跟服务器上的应用程式进行连接,IP地址可以帮助我们找到服务器地址,而端口号[5]则可以帮我们找到执行在服务器上的应用程式

收发阶段

当我们成功建立连接后,资料就可以透过socket在两个应用程式之间流通,接着我们可以透过使用read()/recv()来获取资料,使用write()/send()来传输资料。read()/write与recv()/send()的不同只差在recv()/send()的输入参数多了一个描述符flag,这个描述符提供操作更多的细节控制选项,不过我们以下还是使用通用的收发socket API → read()/write

发送消息

目的: 将资料写入 Socket 中并发送出去

就是这间了,赶紧下单!

程式案例
ssize_t write(int fd, const void *buf, size_t nbyte);
fd是描述符buf是写入资料的缓冲区,使用const修饰参数buf防止内容被更改nbyte是buffer大小
#include <stdio.h>  #include <stdlib.h>  #include <stdint.h>  #include <string.h>  #include <malloc.h>#include <unistd.h>#include <sys/types.h>   #include <sys/socket.h>  #include <netinet/in.h>define MAX 1024char* buf=(char*)malloc(max); // bufferint main(argc, char* argv[]){ssize_t s_write;/*创建socket部分*//*socket连线部分*//*socket write*/strcpy(buf, "socket test");s_write = write(socket_identifier, buf, MAX);if(s_write < 0){printf("socket write error!\n");}else{printf("socket write data length=%d\n", s_read); // 送出了多少资料长度}...return 0;}

透过调用write函式将资料发送出去,回传值可以判断发送的资料长度,若是buffer大小为0会返回0,失败则回传-1。它跟待会要介绍的接收消息read()其实就是两个死对头,一个急着将资料压到buffer里,一个忙着将资料拿出来发出去

接收消息

目的: 透过连线中的 Socket读取资料

系统提示~您已经下订成功!

程式案例
ssize_t read(int fd, void* buf, size_t nbyte);
fd前面讲过了buf是读取资料的缓冲区,socket就是把资料推送到这里nbyte是buffer大小
#include <stdio.h>  #include <stdlib.h>  #include <stdint.h>  #include <string.h>  #include <malloc.h>#include <unistd.h>#include <sys/types.h>   #include <sys/socket.h>  #include <netinet/in.h>define MAX 1024char* buf=(char*)malloc(max); // bufferint main(argc, char* argv[]){ssize_t s_read;/*创建socket部分*//*socket连线部分*//*socket read*/s_read = read(socket_identifier, buf, MAX);if(s_read < 0){printf("socket read error!\n");}else{printf("socket read data length=%d\n", s_read); // 读取buffer内资料长度}...return 0;}

藉由操作read()可以透过回传值得知读取状况,负数代表有错误产生,0或者正数代表读取buffer的资料长度
假如我们得到的回传值是0可能有几个特别意思:

buffer空空如也,甚么都没有通讯双方的socket domain不一致,就是上面创建socket讲的AF_INET, AF_INET6那些通讯双方突然有然段开连线时

关闭阶段

目的: 关闭socket

若完成订房,请登出帐号

程式範例

int close(int fd);
fd不用多说了吧
#include <stdio.h>  #include <stdlib.h>  #include <stdint.h>  #include <string.h>  #include <malloc.h>#include <unistd.h>#include <sys/types.h>   #include <sys/socket.h>  #include <netinet/in.h>int main(argc, char* argv[]){int s_close;/*创建socket部分*//*socket连线部分*//*socket的收发操作*//*关闭socket*/s_close = close(socket_identifier);(s_close < 0) ? printf("socket close error!") : printf("close successfully!");return 0;}

藉由socket断开双方之间的通讯,执行成功返回0,若发生错误则返回-1

[1] :网路是怎样连接的(四)DNS
[2] :传输层使用TCP协议
[3] :关于socket连线部分将在介绍传输层时介绍
[4] :标示符是应用程式用来识别众多本地端socket用
[5] :客户端服务端通讯间用来识别众多对方socket用


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章