Socket编程(二)回射客户/服务器的实现

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;

}

猜你喜欢

转载自blog.csdn.net/weixin_43279714/article/details/84107423