socket编程
一、socket基础
多数操作平台都提供了一组接口使得程序员可以像使用文件一样读写socket。socket通信的基本过程为:
(1)客户端: 申请socket-> connect到服务器(阻塞直到连接成功或者出错) -> 开始读写 -> 读写完毕关闭socket
(2)服务器: 申请socke -> bind 将socket绑定到端口号 -> listen,通知内核这是服务端,开始监听 -> accept,阻塞,直到发现新连接,成功后返回一个新建的已连接sockfd -> 处理客户端请求,对sockfd指向的socket进行读写 -> 服务完毕关闭socket
在读写数据时,如果一端将socket关闭,那么会发生EOF,同时在另一端会读到EOF,即使用系统read函数会得到返回值0
二、伯克利socket
多数操作系统都提供的这个socket编程接口是由加州大学伯克利分校最早提出的,包含了几个基本的函数接口,来实现socket编程,unix函数原型如下
struct in_addr { unsigned int s_addr; //IP地址(IPv4) }; struct hostent { char *h_name; //官方域名 char **h_aliases; //0结束的一组域名(别名) int h_addrtype; //主机地址类型(AF_INET) int h_length; //主机地址长度,通常是4(IPv4) char **h_addr_list; //一组以0结尾in_addr结构体,即ip地址 }; #include <netdb.h> struct hostent* gethostbyname(const char *name); //若成功返回非NULL指针,失败返回NULL,同时设置h_errno struct hostent* gethostbyaddr(const char *addr, int len, 0); //若成功返回非NULL指针,失败返回NULL,同时设置h_errno //connect, bind, accept函数使用的通用结构 struct sockaddr { unsigned short sa_family; char sa_data[14]; }; struct sockaddr_in { unsigned short sin_family; //地址类型,总是AF_INET unsigned short sin_port; //端口号 struct in_addr sin_addr; //IP地址 unsigned char sin_zero[8]; //填充位 }; //申请一个半打开的sock,类型为AF_INET(即Internet), SOCK_STREAM表示协议为TCP int socket(int domain, int type, int protocol); //尝试与serv_addr指向的socket地址建立连接 int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); //成功则返回0,出错则返回-1,会阻塞在此处 //尝试与hostname上的服务器建立连接,并且监听端口port,返回一个可以读写的sockfd,如果出错返回-1,DNS错误返回-2 int open_clientfd(char *hostname, int port) { int clientfd; struct hostent *hp; struct sockaddr_in serveraddr; if ((clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { return -1; } if ((hp = gethostbyname(hostname)) == NULL) { return -2; } memset(&serveraddr, 0, sizeof(serveraddr)); // 初始化 serveraddr.sin_family = AF_INET; //协议类型为Internet memcpy(hp->h_addr_list[0], &serveraddr.sin_addr, hp->h_length); //填入IP与端口 serveraddr.sin_port = htons(port); if (connect(clientfd, (SA*) &serveraddr, sizeof(serveraddr)) < 0) { return -1; } return clientfd; } //bind, listen, accept为服务器端函数 //bind函数要求系统将my_addr中的socket地址与socket描述符联系起来 int bind(int sockfd, struct sockaddr *my_addr, int addrlen); //成功返回0,否则返回-1 //listen告诉系统,这是服务器端 int listen(int sockfd, int backlog); //成功返回0,否则返回-1,backlog是最大apendding连接的数量 //同理可以将服务器端建立sock的函数封装到一起 int open_listenfd(int port) { int listenfd, optval = 1; struct sockaddr_in serveraddr; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { return -1; } //让服务器在重启之后能立即响应客户端请求连接(否则重启后立即bind会"Address already in use") if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(int)) < 0) { return -1; } memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons((unsigned short)port); if (bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) < 0) { return -1; } if (listen(listenfd, LISTENQ) < 0) { return -1; } return listenfd; } //服务器通过调用accept来等待客户端的连接请求,若成功返回一个非负已连接描述符,否则返回-1 int accept(int listenfd, struct sockaddr *addr, int *addrlen); //服务器会阻塞在此处
浙公网安备 33010602011771号