在本篇文章中,先介绍一下Socket编程的一些API,而后利用这些API实现一个客户端-服务器模型的一个简单通讯例程。该例子中,服务器接收到客户端的信息后,将信息从新发送给客户端。编程
socket()函数用于建立一个套接字。这就好像购买了一个电话。不过该电话尚未分配号码。服务器
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol)
参数说明:网络
domain:指定通讯的协议族,这些协议族定义在头文件< sys/socket.h >中。使用IPV4协议族时,该参数设置为AF_INET。dom
type :指定socket的类型。在上一篇文章中介绍过,套接字经常使用的有三种类型:流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW。socket
protocol : 该参数指定了一种协议类型用于所选择的套接字。若是仅有一种协议支持某种套接字类型,那么该参数能够定义为0,此时使用默认协议;若是一种套接字类型可能有多种协议类型,那么必须显式指定协议类型。关于具体细节,能够man socket进行查阅。函数
socket()的返回值:成功时返回非负整数;失败时返回-1;ui
bind()函数绑定一个本地地址到套接字上,这至关于为电话绑定了号码。当一个套接字经过socket()被建立,它并无绑定到具体的地址上,bind()来完成这个步骤。 bind()函数的函数原型以下:code
#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:接口
sockfd:socket()函数建立后成功返回的套接字队列
addr : 须要绑定的地址
addrlen:套接字的大小
这里须要使用到sockaddr_in结构来表示一个地址,该结构以下:
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; }
sockaddr_in须要强制转换为struct sockaddr*类型,传递给bind()函数的第二个参数。下面是一段例程:
int main() { int listenfd = socket(AF_INET,SOCK_STREAM,0); if(listenfd == -1) err_exit("socket error"); struct sockaddr_in addr; //填充结构 addr.sin_family = AF_INET; addr.sin_port= htons(8001); //主机字节序转换为网络字节序 addr.sin_addr= htonl(INADDR_ANY);//绑定主机的任一个IP地址 /*下面两句具备相同的功能:都是绑定到本机ip地址*/ //inet_aton("127.0.0.1",&addr.sin_addr); //addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(bind(listenfd,(const struct sockaddr*)&addr,sizeof(addr))==-1) err_exit("bind error"); }
当使用socket()建立了一个套接字时,该套接字默认是主动套接字。使用listen()函数会使套接字称为一个被动套接字,也就是说,该套接字将被用来接受链接的数据,这些数据经过accept()函数接收。
listen()函数的函数原型以下:
#include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog);
参数说明:
sockfd : 套接字。
backlog: 指定链接队列的长度。
对于给定的监听套接字,内核须要维护两个队列:
已完成链接队列:该队列中的链接处于ESTABLISHED状态,也便是已经完成了三次握手过程。
未完成链接队列:该队列中的链接处于SYN_RCVD状态,还未创建链接。
两个队列的长度之和不可以超过backlogi。若是一个链接请求到达时未完成队列已满,客户端可能接收到一个错误指示ECONNREFUSED。服务器使用accept()函数从已完成链接队列的队头返回一个链接。下面是TCP为监听套接口维护的两个队列:
accept()函数用于从已完成队列的队头返回一个链接。它的函数原型为:
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
sockfd : 服务器套接字
addr :用于接收对等方(客户端)的套接字地址。该参数填充为NULL时,不接收任何信息。
addrlen:返回对等方的套接字地址长度。若是不关心能够设置为NULL,不然必定要初始化。
函数返回值:成功返回一个非负整数,表明一个套接字;失败返回-1;
该函数用于创建一个链接到指定的套接字。函数的原型为:
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd : 未链接的套接字
addr:未链接的套接字地址
addrlen:addr的长度
客户端代码:
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { /*建立一个套接字*/ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock == -1) ERR_EXIT("socket"); /*定义一个地址结构*/ struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5888); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /*进行链接*/ if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("connect"); } else { printf("链接成功\n"); } char sendbuf[1024]={0}; char recvbuf[1024]={0}; /*从标准输入中读入*/ while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(sock ,sendbuf,strlen(sendbuf)); if(read (sock,recvbuf,sizeof(recvbuf))>0) { printf("从服务器接收信息:\n"); fputs(recvbuf,stdout); } memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); } close(sock); return 0; }
服务器端代码:
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<arpa/inet.h> #include<netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include<string.h> #define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { /* 建立一个套接字*/ int listenfd= socket(AF_INET ,SOCK_STREAM,0); if(listenfd==-1) ERR_EXIT("socket"); /*定义一个地址结构并填充*/ struct sockaddr_in addr; addr.sin_family = AF_INET; //协议族为ipv4 addr.sin_port = htons(5888); //绑定端口号 addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机字节序转为网络字节序 /*将套接字绑定到地址上*/ if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1) { ERR_EXIT("bind"); } /*监听套接字,成为被动套接字*/ if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("Listen"); } struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); /*定义一个套接字,一般称为已链接套接字*/ int conn ; conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn <0) ERR_EXIT("accept error"); else printf("链接到服务器的客户端的IP地址是:%s,端口号是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port)); /*循环获取数据、发送数据*/ char recvbuf[1024]; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(conn,recvbuf ,sizeof(recvbuf)); fputs(recvbuf,stdout); write(conn,recvbuf,sizeof(recvbuf)); } /*关闭套接字*/ close(listenfd); close(conn); return 0; }