思考重点
- 如何将应用程序消息委託给协议栈发送?
- socket是调用那些函式进行收发操作?
核心知识

协议栈如何进行收发操作
现在将拥有的数据整理一下,首先HTTP消息封包已经由应用程式打包完成,服务器IP地址也已经透过DNS1请求机制获得。在两个前提条件都满足的状况下,我们就可以着手思考要怎麽将这些数据发给对方服务器的应用程式
发送数据其实是调用多个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或ICMP
- protocol
- 通常在设定完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 1
int 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]; // 不会用到
};
- fd
- socket的描述符
- 其实就是上面创建socket的回传值标示符socket_identifier
- server
- sockaddr结构体,负责提供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 8080
typedef 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_identifier4,接下来的任务就是要跟服务器上的应用程式进行连接,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 1024
char* buf=(char*)malloc(max); // buffer
int 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 1024
char* buf=(char*)malloc(max); // buffer
int 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