socket 老规矩膜佬
前面我们学习了各种UNIX系统所提供的经典进程间通信机制(IPC):管道、FIFO、消息队列、信号量以及共享存储。这些机制允许在同一台计算机.上运行的进程可以相互通信。
而现在我们来学习不同设备(通过网络相连)上的进程相互通信的机制:网络进程间通信(network IPC)。
什么是 socket
socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
socket 的典型应用就是 Web 服务器和浏览器:浏览器获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。
学习 socket,也就是学习计算机之间如何通信,并编写出实用的程序。
linux 下
- socket() 函数, 创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
作用 : 创建一个套接字 其返回值为 UNIX 显然为 文件描述符
demain : 指明所使用的协议,通常为 AF_INET , 表示互联网协议族(TCP/IP 协议族);
1. AF_INET IPv4 因特网域
2. AF_INET6 IPv6 因特网域
3. AF_UNIX UNIX域
4. AF_ROUTE 路由套接字
5. AF_KEY 密钥套接字
6. AF_UNSPEC 未指定
type 参数指定 socket 的类型: // 这个世界上有很多种套接字(socket),
// 本教程只讲第一种套接字——Internet 套接字,它是最具代表性的,也是最经典最常用的。
1. SOCK_STREAM
流格式套接字,也叫“面向连接的套接字”,
是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。流格式套接字有自己的纠错机制,在此我们就不讨论了。
使用 TCP 协议,从而保证数据传输的正确性和顺序性,
实际应用场景:浏览器所使用的 http 协议就基于面向连接的套接字,因为必须要确保数据准确无误,否则加载的 HTML 将无法解析。
特征:
数据在传输过程中不会消失;
数据是按照顺序传输的;
数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。
2. SOCK_DGRAM
数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,在代码中使用 SOCK_DGRAM 表示。
计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。
因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。
特征:
强调快速传输而非传输顺序;
传输的数据可能丢失也可能损毁;
限制每次传输的数据大小;
数据的发送和接收是同步的(有的教程也称“存在数据边界”)。
就是 UDP
3. SOCK_RAW
允许程序使用底层协议,原始套接字允许对底层协议 如 IP 或 ICMP 进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发
protocol 通常赋值 0
0 选择 type 类型对应的默认协议,
IPPROTO_TCP TCP 传输协议
IPPROTO_UDP UDP 传输协议
IPPROTO_SCTP SCTP 传输协议
IPPROTO_TIPC TIPC 传输协议
- bind() 函数,将端口号,IP地址 与 套接字 建立本地捆绑
linux下:
#include <sys/types.h>
#include <sys/socket.h>
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
作用:绑定端口号,IP地址 到 套接字的文件描述符(socketfd)
参数:
sockfd :一个 socket 的描述符
addr : 是一个指向包含有本机 IP地址 及 端口号 的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构,
IPv4 的结构体为:
struct sockaddr{
unsigned short sa_family; // address family, AF_xxx 就是指协议族
char sa_data[14]; // 14 bytes of protocol address 指 IP + 端口号
};
同等替换
一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中。 强制类型转换时,需要 指针转换 (void *) (char *)
比如说,
struct sockaddr_in sock_addr;
bind(sock_fd,(struct sockaddr *)&sock_addr,sizeof(struct sockaddr_in));
struct sockaddr_in{
short int sin_family; /* Address family */ 协议族
unsigned short int sin_port; /* Port number */ 存储端口号
struct in_addr sin_addr; /* Internet address */ 存储IP地址
struct in_addr {
_be32 s_addr;
}; 其实就是在存放 IP地址的结构体。
unsigned char sin_zero[8]; /* Same size as struct sockaddr */ 是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。 让 sockaddr_in 能够与 sockaddr 在内存中对齐,实现相互转换
};
int inet_aton(const char * straddr,struct in_addr* addrp);
作用 : 把字符串形式的 " 192.168.1.123" 转换为 网络能识别的格式
int inet_ntoa(struct in_addr inaddr);
作用 : 把网络格式的 IP地址转为 字符串 形式
struct in_addr {
in_addr_t s_addr;
};
表示一个32位的IPv4地址。
#include <sys/socket.h>
int listen( int sockfd, int backlog);
sockfd:用于标识一个已捆绑未连接套接口的描述字。
backlog:等待连接队列的最大长度。
返回值 : 如无错误发生,listen()返回 0。
否则的话,返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。
功能 : 设置能处理的最大连接数,listen() 并未开始接受连线,只是设置 socket 的 listen 模式,
listen 函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动要求与某个进程连接,
而只是一直监听 是否有其他客户进程与之连接,然后响应连接请求,并对它做出处理,一个服务进程可以同时处理多个客户的连接,
主要就两个功能 : 将一个未连接的套接字 转换 为一个被动套接字(监听),规定内核为响应套接字排队的最大连接数。
内核为任何—个给定监听套接字维护两个队列:
未完成连接队列,每个这样的 SYN 报文对应其中的一项,已由某个客户端发出并达到服务器,而服务器正在等待完成相应的 TCP 三次握手过程,这些套接字处于 SYN_REVD 状态。
参数
已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项,这些套接字处于 ESTABLOSHED 状态。
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr * addr,aocklen_t * addrlen);
功能 : accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接,
如果已完成连接队列为空,那么进程被投入睡眠。
参数 : sockfd
addr 用于 返回已连接的对端(客户端)的协议地址
addrled 客户端地址长度
返回值:
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。
一个服务器仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在,内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成给某个 给定客户的服务时,相应的已连接套接字就会被关闭。
connect()用于建立与指定socket的连接。
头文件: #include <sys/socket.h>
函数原型: int connect(SOCKET s, const struct sockaddr * name, int namelen);
参数:
s:标识一个未连接socket
name:指向要连接套接字的sockaddr结构体的指针
namelen:sockaddr结构体的字节长度
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
// #include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(){
// 1. socket int socket(int domain, int type, int protocol);
// 2. bind int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
// 3. listen
// 4. accept
// 5. read
// 6. write
// 1. socket
int sock_fd = socket(AF_INET,SOCK_STREAM,0); // 选择 IPv4 流格式套接字 0默认协议
if(sock_fd == -1){
printf("socket is error \n");
exit(-1);
}
// 2. bind
struct sockaddr_in sock_addr;
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(7878); // 不要使用低于 3000 的,一般 3000 以下都是系统的常用端口号,我们一般用 5K 到 9K
// 注意这个端口号,我们用的不是整形,因为>传输数据的问题,所以我们往往要使用其他函数来处理,使整形转换为 网络字节序
inet_aton("192.168.43.228",&sock_addr.sin_addr);
// 显然这里也要转换字符串成为 网络能够识别
// 重中之重,这里的 IP 地址,在linux 用 ifconfig 进行查阅,
// 这个结构体 struct sockaddr_in 包含一个结构体,in_addr 我们需要查阅一下 其实就是 网络 IP 地址
bind(sock_fd,(struct sockaddr *)&sock_addr,sizeof(struct sockaddr_in));
// 3. listen
listen(sock_fd,10);
// 4. accept
int ac_fd = accept(sock_fd,NULL,NULL);
// 5. read
// 6. write
printf("ok\n");
while(1);
return 0;
}
代码跑起来以后,cmd端,
显示出: