网路是怎样连接的(五)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或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_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 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

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


<<:  Day 23 利用transformer自己实作一个翻译程序(五) Positional encoding

>>:  Day 23 ASP.NET Core Identity 说明

苹果电脑密技:如何快速清除 Mac 快取/暂存档案?

当我们在 Mac 上打开和使用应用程序(如Google Chrome 浏览器等)或其他文件时,系统会...

【day18】聊天室(上) X Realtime database

好的,今天我们要来看的就是我们的精华啦-聊天室。 原本我们在设计邀约流程的时候是。 (原本设想的流程...

[ Day 24 ] React 中的样式设定

今天要介绍的内容是如何在 React.js 中撰写我们的 CSS 样式?除了相关套件的应用之外,还...

DAY10 Line Messaging API 的各种讯息格式

传送文字 在设定回覆讯息的部分,如果是文字讯息的话,是这样写: events.message.typ...

[Python 爬虫这样学,一定是大拇指拉!] DAY05 - URL / URN / URI (1)

本篇章主要是先从技术层面来解释 URI 与 URL 及比较少使用到却与生活息息相关的 URN,让读者...