0. 套接字函数
1.socket函数
为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型(IPv4的TCP,IPv6的UDP,Unix域字节流协议等)
#include<sys/socket.h> int socket ( int family , int type , int protocol); 返回 : 非负的描述符 ------ 成功,-1 ----- 失败-
family : 指明协议族, (AF_INET,IPv4协议) , (AF_INET,IPv6协议),......
type:指明套接口类型, (SOCK_STREAM,字节流套接口),(SOCK_DGRAM,数据报套接口),......
protocol:指明协议类型常值,(IPPROTO_TCP,TCP传输协议),(IPPROTO_UDP,UDP传输协议),(IPPROTO_SCTP,SCTP传输协议),(0,family和type组合的系统缺少值).
成功则返回一个套接口描述字
2.connect函数
TCP客户用connect函数来建立与TCP服务器的连接
#include<sys/socket.h> int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen) 返回:0------成功 -1 ------失败
sockfd : socket函数返回的套接字描述符
servaddr:套接口地址结构(必须含有服务器的地址和端口号)的指针
addrlen:该结构的大小
如果客户端没有绑定端口号,则内核会确定源IP地址,并选择一个临时端口作为源端口
如果是TCP套接口,调用connect函数将激发TCP的三次握手,而且仅在连接建立成功或出错时才返回,其中出错可能有以下几种情况:
- 若TCP客户没有收到SYN分节的响应,则返回ETIMEOUT错误.会重复发送3次,如果3次都没有响应则返回该错误
- SYN响应为RST(表示复位),表示服务器在该端口上没有进程在等待连接,返回ECONNREFUSED错误.RST产生的3个条件:1.目的地为某端口的SYN到达,然而该商品上没有正在监听的服务器.
2.TCP想取消一个已有的连接
3.TCP接收到一个根本不存在的连接上的分节(TCPv1第246-250页有更详细的信息) - SYN在中间的某个路由器上引发一个目的地不可达的ICMP错误,主机内核保存错误并继续发送SYN,若在某个规定的时间后仍未收到响应,则把保存的消息作为
EHOSTUNREACH或ENETUNREACH错误返回给进程.
3.bind函数
bind函数把一个本地协议地址赋给一个套接口.
本地协议地址(32位IPv4或128位的IPv6,16位UDP或TCP端口号)
#include<sys/socket.h> int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen); 返回:0------成功,-1------失败
myaddr:套接口地址结构的指针
addrlen:该结构的大小
调用bind可以指定IP地址和端口,可以两者都指定,也可以都不指定
IP地址 | 端口 | 结果 |
通配地址 | 0 | 内核选择IP地址和端口 |
通配地址 | 非0 | 内核选择IP地址,进程选择端口 |
本地IP地址 | 0 | 进程选择IP地址,内核选择端口 |
本地IP地址 | 非0 | 进程指定IP地址和端口 |
通过getsockname()来返回实际的协议地址
4.listen函数
listen函数仅由TCP服务器调用,它做两件事情:
1.当socket函数创建一个套接口时,它被假设为一个主动的套接口,也就是说,它是一个即将调用connect发起连接的客户端套接口.listen函数把一个未连接的套接口转换成为一个被动的套接口,指示内核应当接收指向该套接口的连接请求.调用listen寻到套接口由CLOSE状态转换为LISTEN状态
2.第二个参数指定内核应该为相应套接口排队的最大连接个数
#include<sys/socket.h> int listen(int sockfd,int backlog); 返回:0------成功,-1------失败
内核为任何一个给定的监听套接字维护两个队列
- 未完成连接队列:某个客户发出SY并到达服务器,而服务器正在等待完成相应的TCP三路握手过程.
- 已完成连接队列:每个已完成TCP三路握手过程的客户对应其中的一项.
5.accept函数
accept函数由TCP服务器调用,用于从已完成连接队列队头取出下一个已完成连接.如果已完成连接队伍为空,那么进程被投入睡眠(假设为阻塞方式)
#include<sys/socket.h> int accept(int sockfd,struct sockaddr* cliaddr,socklen_t* addrlen); 返回:非负描述符------成功,-1------失败
cliaddr:接收对端客户的协议地址.
addrlen:调用前整数值置为cliaddr地址结构的大小,调用后返回实际接口地址结构内确切的字节数
若返回成功,则内核为每个已连接的客户创建一个已连接的套接口.
简单的服务器时间回显程序
#include<time.h> #include<iostream> #include<sys/socket.h> #include<netinet/in.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #include<cassert> #include<unistd.h> #include<arpa/inet.h> using std::cout; class TCP { public: bool Socket() { listenfd = socket(AF_INET,SOCK_STREAM,0); return listenfd >= 0; } bool Bind(int port) { memset(&servaddr,0,sizeof(servaddr)); //清空结构体 servaddr.sin_family = AF_INET; //指定协议 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //通配地址 servaddr.sin_port = htons(port); //指定端口号 int bind_ret = -1; if(listenfd >= 0) bind_ret = bind(listenfd,(sockaddr *)&servaddr,sizeof(servaddr));//绑定指定端口 else { cout << "listenfd < 0 !!!\n"; return false; } return bind_ret == 0; } bool Listen(int len){ return listen(listenfd,len) == 0; } int Accept() { socklen_t len = sizeof(cliaddr); int connfd = accept(listenfd,(sockaddr *)&cliaddr,&len); //从已完成连接队列取出首个,若没有则阻塞 cout << "connect from " << inet_ntop(AF_INET,&cliaddr.sin_addr,buf,sizeof(buf)) << ", port " << ntohs(cliaddr.sin_port) << "\n"; return connfd; } bool Close(int connfd) { close(connfd); return true; } private: int listenfd; struct sockaddr_in servaddr,cliaddr; char buf[1024]; }; int main() { TCP tcp; bool r = tcp.Socket(); assert(r == true); r = tcp.Bind(1027); assert(r == true); r = tcp.Listen(30); while(true){ int connfd = tcp.Accept(); time_t ticks = time(0); char buf[128]; snprintf(buf,sizeof(buf),"%.24s\r\n",ctime(&ticks)); write(connfd,buf,strlen(buf)); tcp.Close(connfd); } return 1; }