TCP客户/服务器模型,即C/S模型
可以将这个模型类比为打电话。
TCP服务器通过socket()建立一个套接字,相当于安装一台话机;然后为安装好的话机绑定bind()一个电话号码;让话机处于监听的状态listen();等待客户端的连接accept(),也就是等待对方电话拨打过来,如果一直没有电话拨打过来,那就一直阻塞,知道客户端连接到达;
对于客户端来说,它也需要安装一部话机socket();接下来调用connect()函数,拨打电话号码;拨通之后就建立了连接,一旦连接建立之后双方就开始进行通讯;
客户端通过write()函数发起请求;服务器通过read()函数接收请求,接收到请求后对请求进行处理;然后服务器通过write()函数将处理的结果向客户端进行应答;客户端通过read()函数读取服务器发来的应答;这是一个循环,如果客户端还有请求可继续发送,服务器会根据客户端发送的请求进行应答;
在通信过程中,任何一端都可以终止通信,通过调用close()函数来终止通信;比如客户端发起一个close,这是一个文件描述符EOF的通知,服务器读到read()通知就会调用close()将自己的套接字关闭掉。
这就是一个基本的C/S模型
回射客户/服务器模型
客户端从标准输入(stdin)选择一行数据,通过网络发送(write)给服务器一端,服务器读取(read)数据,不做任何处理,将数据原封不动的发送(write)给客户端,客户端读取数据(read)并标准输出(stdout)。
这是一个基本的回射客户/服务器模型,下面是实现这个模型所用到的函数:
socket函数
包含头文件:<sys/socket.h>
功能:创建一个套接字用于通信
函数原型:int socket(int domain,int type,int protocol);
相关参数:domain指定通信协议族;type指定socket类型,流式套接字SOCK_STREAM、数据包套接字SOCK_DGRAM、原始套接字SOCK_RAW;protocol指定协议类型。
返回值:成功返回非负整数,它与文件描述符类似,我们把它称为套接口描述字,简称套接字;失败返回-1 .
bind函数
包含头文件:<sys/socket.h>
功能:绑定一个本地地址到套接字
函数原型:int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen); //将IPv4的地址强制转化为通用地址长度
相关参数:sockfd是socket函数返回的套接字;addr是要绑定的地址;addrlen是地:址长度。
返回值:成功返回0,失败返回-1;
listen函数
包含头文件:<sys/socket.h>
功能:将套接字用于监听进入的连接,将套接字从close状态转换成监听(listen)状态
函数原型:int listen(int sockfd,int backlog);
相关参数:sockfd是socket函数返回的套接字;backlog规定内核为此套接字排队的最大连接个数。
返回值:成功返回0,失败返回-1.
一旦调用listen函数后,这个套接字就变成了被动套接字,否则默认是主动套接字。主动套接字是用来发起连接的(通过调用connect函数),被动套接字是用来接收连接的(通过调用accept函数)。
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:已由客户端发出并到达服务器,服务器正在等待完成响应的TCP三次握手过程;已完成连接的队列。
连接需要通过三次握手,三次握手时会发送一些SYN分节过来,这时需要两个队列来维护,一是未完成连接队列,一旦三次握手成功之后,就会将这个分节移动到已完成连接队列里边。listen函数的第二个参数backlog代表未完成连接队列与已完成连接队列之和。服务器调用accept函数之后就会从已完成连接队列取走一个SYN分节,以便更多的客户端可以发起连接。所以backlog规定了能够并发连接过来的套接口数目,它的值等于未完成连接数目和已完成连接数目。
accept函数
包含头文件:<sys/socket.h>
功能:从已完成队列返回第一个连接,如果已完成连接队列为空,则阻塞。
函数原型:int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);
相关参数:sockfd是服务器套接字;addr将返回对等方的套接字地址;addrlen返回对等方的套接字地址长度。
返回值:成功返回非负整数,表示创建了一个新的套接字;失败返回-1。
connect函数
包含头文件:<sys/socket.h>
功能:建立一个连接至addr所指定的套接字
函数原型:int connect(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
相关参数:sockfd是未连接的套接字;addr是要连接的套接字地址;addrlen是第二个参数addr的长度。
返回值:成功返回0,失败返回-1.
以下是回射客户/服务器代码
服务器端echo-server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
int main(void)
{
//create a socket
int sockfd;
if((sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0) //前两个参数已经决定是TCP协议
//第三个参数可以直接写0,如下
/*if((sockfd=socket(AF_INET,SOCK_SCREAM,0))<0); //SOCK_SCREAM TCP 0 */
ERR_EXIT("socket error!");
//initialize address
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188); //将主机字节序转化为网络字节序,s代表两个字节。
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //0.0.0.0 all of the host address
//这里的htonl可以省略掉,因为全0,转化也为0
/*servaddr.sin_addr.s.addr=inet_addr("127.0.0.1"); //将点分式十进制地址转化为32位地址*/
/*inet_aton("127.0.0.1",&servaddr.sin_addr); //将点分式十进制地址转化位网络地址*/
//bind将套接字与本地地址进行绑定
if(bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind error!");
//listen将套接字用于监听进入的连接
if(listen(sockfd,SOMAXCONN)<0) //SOMAXCONN这个宏代表队列最大值
ERR_EXIT("listen error!");
//accept从已完成连接队列的队头得到一个连接
struct sockaddr_in peeraddr; //define peer address
socklen_t peerlen=sizeof(peeraddr); //对方的地址长度
int conn; //define a new socket,the accept will return it.
if((conn=accept(sockfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept error!");
//通信
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret=read(conn,recvbuf,sizeof(recvbuf));//读取新的套接字,这个套接字由accept返回
fputs(recvbuf,stdout);
write(conn,recvbuf,ret);
}
close(conn);
close(sockfd);
return 0;
}
客户端echo-client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
int main(void)
{
//create a socket
int sock;
if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
/*if((sock=socket(AF_INET,SOCK_SCREAM,0))<0); //SOCK_SCREAM TCP 0 */
ERR_EXIT("socket error!");
//initialize address
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//连接服务器端的地址
//不需要绑定、监听,直接连接即可
//connect发起连接
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect error!");
//通信
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) //从标准输入接收一行数据
{
write(sock,sendbuf,strlen(sendbuf));
read(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout); //回射数据
//clear the buf
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;
}