1. socket
1.1原型
#include <sys/socket.h>
int socket (int family ,int type ,int protocol)
family: AF_INET(ipv4) AF_INET(ipv6) AF_LOCAL (Unix域协议) AF_ROUTE(路由套接字) AF_KEY(秘钥套接字)
type: SOCK_STREAM(字节流套接字) SOCK_DGRAM(数据包套接字) SOCK_SEQPACKET(有序分组套接字) SOCK_RAW(原始套接字)
protocol:IPPROTO_TCP IPPROTO_UDP IPPROTO_SCTP
1.2 family和type参数组合
AF_INET | AF_INET6 | AF_LOCAL | AF_ROUTE | AF_KEY | |
---|---|---|---|---|---|
SOCK_STREAM | TCP/STCP | TCP/STCP | 是 | ||
SOCK_DGRAM | UDP | UDP | 是 | ||
SOCK_SEQPACKET | SCTP | SCTP | 是 | ||
SOCK_RAW | IPV4 | IPV6 | 是 | 是 |
1.3 调用函数
IPV4的TCP通信 :
int sockfd;
if( (sockfd = socket(AF_INET,SOCK_STREAM,0)) <0) {}
2. connect(客户端)
2.1 函数原型
#include <sys/socket.h>
int connect (int sockfd ,const struct sockaddr *servaddr ,socklen_t addrlen)
sockfd 套接字描述符servaddr 通用套接字地址结构
socklen_t sizeof(servaddr )
2.2 三次握手
假设 A 为客户端,B 为服务器端。
首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。
A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。
B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时
也选择一个初始的序号 y。
A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
B 收到 A 的确认后,连接建立。三次握手的原因
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。
2.3 出错的情况(网络阻塞,端口,路由)(超时、拒绝服务)
TCP客户端没有接收到SYN分节的响应,则返回 ETIMEDOUT
客户端接收到RST 的响应,则表明服务器主机在我们指定的端口上没有进程在监听或者服务器上的监听队列已满,返回ECONNREFUSED
若客户发出的SYN在中间的某个路由器上引发一个“ destination unreachable”的ICMP错误,
EHOSTUNREACH *ENETUNREACH
2.4 调用函数
struct sockaddr_in servaddr;//定义一个套接字结构(IPV4)
//初始化套接字结构,TCP协议,**server IP and port**
bzero(&servaddr,sizeof(servaddr));//清0操作
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5330);//port
if( inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0){ }
//客户端发起连接,struct sockaddr *通用套接字结构
if ( connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0) {}
3. bind(服务端)
3.1函数原型
#include <sys/socket.h>
int bind (int sockfd,const struct sockaddr *myaddr,socklen_t addrlen)
调用bind函数可以指定一个端口号/IP地址( IP, Port)
如果一个TCP客户端或者服务器未调用bind,则调用connect或者listen时,内核就要为相应的套接字选择一个临时端口。
一般情况下,TCP客户端默认不调用bind,服务器调用bind。
3.2 出错的情况
常见错误:EADDRINUSE(Address already in use)
3.3 调用函数
// server
//初始化套接字结构,TCP协议,server IP and port
bzero(&servaddr,sizeof(servaddr));//清0操作
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5330);//port
//内核决定IP地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY == 0
if( bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){}
4.listen
4.1 函数原型
#include <sys/socket.h>
int listen(int sockfd, int backlog);
backlog:可连接的TCP数量,内核为相应套接字排队的最大连接个数
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
拓展知识
服务器TCP连接通道 数 = 未完成连接队列 + 已完成连接队列
调用listen成功,套接字从CLOSED切换到LISTEN
SYN_RCVD (未完成连接队列 ) ,由某个客户端发出SYN分节,三次握手的第一阶段,当服务器接收到SYN时,由LISTEN切换到SYN_RCVD.
当服务器完成三次握手,SYN_RCVD切换到ESTABLISHED
4.2 出错的情况
EBADF
The argument sockfd is not a valid file descriptor.
EADDRINUSE
Another socket is already listening onthe same port.
4.3 调用函数
if(listen(sockfd,10) <0){}
5.accept
5.1 函数原型
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 参数数据类型参考上面 addr 是客户端的套接字结构,可查看客户端的IP和端口号 成功时,返回一个已连接描述符
5.2 出错的情况
查看:man accept
里面涉及十多种错误
5.3 调用函数
int connfd;
if( (connfd = accept(sockfd,(struct sockaddr *)NULL,NULL) ) <0){}
//查看客户端信息
struct sockaddr_in clientaddr;
char buff [100];
socklen_t len =sizeof(clientaddr);
if (connfd = accept(sockfd,(struct sockaddr *)&clientaddr,&len) <0){}
printf("client ip and port,%s ,%d\n",inet_ntop(AF_INET,&clientaddr.sin_addr,buff,sizeof(buff)),ntohs(clientaddr.sin_port));
6.close
6.1 函数原型
#include <unistd.h>
int close (int sockfd);使用后,描述符不能作为read或者write的参数。
6.2 调用函数
if( close (connfd)<0) {}
7.socket总结
客户端 |
---|
int socket (int family ,int type ,int protocol) |
int connect (int sockfd , const struct sockaddr *servaddr , socklen_t addrlen) |
int close (int sockfd); |
服务器 |
---|
int socket (int family ,int type ,int protocol) |
int bind (int sockfd,const struct sockaddr *myaddr,socklen_t addrlen) |
int listen(int sockfd, int backlog); |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
int close (int sockfd); |