SOCKET 解析
进程间的通信方式
SOCKET是什么
socket起源于Unix,而Unix/Linux中 “一切皆文件”,都可以用“open –> write/read –> close”模式来操作。我认为 Socket也基于该模式的一个实现,socket 打开的是一种特殊的文件
ios协议栈
socket 鉴于应用层和传输层之间,是linux 统一为应用层提供的一个公共接口。它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议如下图
SOCKET 相关操作函数简介
socket函数解析
/*
domain: 即协议域,又称为协议族(family)。
常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、
AF_ROUTE(路由套接字用于在内核路由表中添加和删除路由)等等。协议族决定了socket的地址类型,在通
信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX
决定了要用一个绝对路径名作为地址本地进程通信
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、
SOCK_SEQPACKET等等
protocol: 故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。protocol
为0时,会自动选择type类型对应的默认协议
protocol 和 type 不能随意搭配,需要根据要求来,如UDP不能用SOCK_STREAM
*/
int socket(int domain, int type, int protocol);
bind 函数解析
在启动的时候都会绑定一个众所周知的地址(将地址按 选定的地址协议族的格式赋给socket。如AF_INET就按IPV4的地址格式 地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。只需要connect()时由系统随机生成一个。
/*
sockfd:socket描述字,它是通过socket()函数创建了,唯一标识一个socket
addr:指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同
struct sockaddr * 指向
ipv4
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr;
};
unix域
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX];
};
addrlen:对应的是地址的长度
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen 函数解析,connect 函数解析
listen:当服务器bind之后,本来socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,设置相关socket fd为监听模式,服务器会监听是否有客户端connect它。
connect:
客户端通过调用connect函数来建立与TCP服务器的连接
/*
sockfd: 为要监听的socket描述字
backflag: 为相应socket可以排队的最大连接个数
*/
int listen(int sockfd, int backlog);
/*
sockfd: 为客户端的socket描述字
addr: 为服务器的socket地址
addrlen: 为socket地址的长度
返回值: 0 成功,其他失败
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
accept 函数解析
/*
sockfd: 服务器的socket描述字
addr: 用于返回客户端的协议地址
addrlen: 协议地址的长度
返回值: 内核自动生成的一个全新的描述字,代表与返回客户的TCP连接
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
读写数据相关函数
调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
close 函数
int close(int fd);
头文件概述
建议:在编写网络程序时,可以直接使用下面这段头文件代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
三次握手和四次挥手
为什么要第三次握手?
防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误和开销
防止网络延迟导致客户端第一个syn包到达服务器时候,客户端已经超时关闭连接了,而服务端还以为关联着,导致某些错误和开销
四次挥手,由一端主动释放,可以是客户端也可以是服务端。本次是客户端
为什么要加一个2ML的time_wait?
当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是Maximum Segment Lifetime:一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长
挥手时候为什么是四次而不是三次
因为要保证数据完全传输完成 先响应关闭链接,然后发完剩余数据后再次确认关闭
实例
tcp sock代码操作流程
服务端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
#define PORT 8820
#define MAX_LINE 2048
#define LISTENQ 20
int main(int argc , char **argv)
{
int i, listenfd, connfd, sockfd;
ssize_t n, ret;
char buf[MAX_LINE];
socklen_t clilen;
struct sockaddr_in servaddr , cliaddr;
/*(1) 得到监听描述符*/
listenfd = socket(AF_INET,SOCK_STREAM,0);
/*(2) 绑定套接字*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
/*(3) 监听*/
listen(listenfd , LISTENQ);
clilen = sizeof(struct sockaddr_in);
/*(4) 进入服务器接收请求死循环*/
while(1){
printf("wait connect\n");
if((connfd = accept(listenfd ,(struct sockaddr *)&cliaddr , &clilen)) < 0)
{
printf("accept error. %d\n", connfd);
continue;
}//if
printf("accpet a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port);
while((n = read(connfd , buf , MAX_LINE)) > 0)
{
printf("recv msg %s\n",buf);
}
printf("recv over\n");
close(connfd);
}
close(listenfd);
}
客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
#define PORT 8820
#define MAX_LINE 2048
#define LISTENQ 20
int main()
{
int i, clientfd;
ssize_t n, ret;
char buf[MAX_LINE];
socklen_t clilen;
struct sockaddr_in servaddr;
/*(1) 得到监听描述符*/
clientfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
/*(2) 连接到服务器*/
int fail = connect(clientfd, (struct sockaddr *)&servaddr ,sizeof(servaddr));
if(fail){
printf("connect fail \n");
return 0;
}
n = write(clientfd, "hello", sizeof("hello"));
printf("send ok\n");
}
udp socket 代码操作流
服务端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
#define PORT 8824
#define MAX_LINE 2048
#define LISTENQ 20
int main(int argc , char **argv)
{
int i, listenfd;
ssize_t n, ret;
char buf[MAX_LINE];
socklen_t clilen;
struct sockaddr_in servaddr , cliaddr;
/*(1) 得到监听描述符*/
listenfd = socket(AF_INET,SOCK_DGRAM,0);
/*(2) 绑定套接字*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while(1){
while((n = recvfrom(listenfd , buf, MAX_LINE,0,(struct sockaddr *)&servaddr,sizeof(servaddr))) > 0)
{
printf("recv msg %s\n",buf);
}
printf("recv over\n");
}
close(listenfd);
}
客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
#define PORT 8824
#define MAX_LINE 2048
#define LISTENQ 20
int main()
{
int clientfd;
ssize_t n;
struct sockaddr_in servaddr;
/*(1) 得到监听描述符*/
clientfd = socket(AF_INET,SOCK_DGRAM,0);
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
/*(2) 连接到服务器*/
int fail = connect(clientfd, (struct sockaddr *)&servaddr ,sizeof(servaddr));
if(fail){
printf("connect fail \n");
return 0;
}
n = sendto(clientfd , "hhh" ,4,0,(struct sockaddr *)&servaddr,sizeof(servaddr));
printf("send ok\n");
close(clientfd);
}