思考重点
如何将应用程序消息委託给协议栈发送?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);
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可能有几个特别意思:
关闭阶段
目的: 关闭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用