Linux网络编程:TCP客户/服务器模型及基本socket函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37413035/article/details/82708952

TCP客户/服务器模型

这里写图片描述

TCP连接的分组交换

这里写图片描述
在使用socket API的时候应该清楚应用程序和TCP协议栈是如何交互的:
调用connect()会发出SYN段(SYN是TCP报文段头部的一个标志位,置为1)
阻塞的read()函数返回0就表明收到了FIN段
客户端调用connect()发起连接,服务器端accept()返回套接字的时候就意味着TCP的三次握手已经完成

TCP11种状态:除了图中的10种状态之外,还有一种状态叫CLOSING,产生的原因是双方同时关闭。

socket函数

创建一个套接字用于通信

#include <sys/types.h>  
#include <sys/socket.h>  
int socket(int domain, int type, int protocol);  

参数:
domain:指定通信协议族(protocol family),常用取值AF_INET(IPv4)
type:指定socket类型, 流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol:协议类型,常用取值0, 使用默认协议

返回值: 成功: 返回非负整数,套接字; 失败: 返回-1

bind函数

绑定一个本地地址到套接字

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
sockfd:socket函数返回的套接字
addr:要绑定的地址

listen函数

listen函数应该用在调用socket和bind函数之后, 并且用在调用accept之前, 用于将一个套接字从一个主动套接字转变成为被动套接字。

int listen(int sockfd, int backlog);  

参数backlog说明:
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程(SYN_RCVD状态)
2、已完成连接的队列(ESTABLISHED状态)
但是两个队列长度之和不能超过backlog。(具体内容在UNP书里有)
这里写图片描述

accept函数:

从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

参数:
sockfd:服务器套接字
addr:将返回对等方的套接字地址, 不关心的话, 可以设置为NULL
addrlen:返回对等方的套接字地址长度, 不关心的话可以设置成为NULL, 否则一定要初始化

返回值: 成功则返回非负描述符,出错则返回-1

connect函数

建立一个连接至addr所指定的套接字

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度

简单的回射客户/服务器模型

这里写图片描述

简单的回射客户/服务器例子

//echosrv.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.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) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int main(void)
{
        //创建一个套接字
        int listenfd; 
        if((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTP_TCP)) < 0) 
                ERR_EXIT("socket");
                
	    //初始化地址
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        
        //在bind之前开启“地址重复利用”(设置套接字选项SO_REUSEADDR避免TIME_WAIT状态的出现)
        int on = 1;
        if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
                ERR_EXIT("setsockopt");
                
        //绑定    
        if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
                ERR_EXIT("bind");

        //监听    
        if (listen(listenfd, SOMAXCONN) < 0)
                ERR_EXIT("listen");     
        
        struct sockaddr_in peeraddr;//对方地址
        socklen_t peerlen = sizeof(peeraddr);
        int conn;//已连接套接字

        //接受连接
        if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
                ERR_EXIT("accept");                

        //连接成功后打印出对方的地址    
        printf("客户端的ip = %s, port = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(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, ret);
        }

	    //关闭套接字
        close(conn);
        close(listenfd);

        return 0;
}                    
//echocli.c
#include <stdio.h>
#include <sys/types.h> 
#include <unistd.h>
#include <sys/socket.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) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int main(void)
{    
        //创建套接字
        int sock; 
        if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)  
                ERR_EXIT("socket");
        
        //初始化所要连接的对方地址
        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");

        //连接
        if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0)   
                ERR_EXIT("connect");
                 
        char sendbuf[1024] = {0};
        char recvbuf[1024] = {0};
        //通信细节
        while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 
        {
                write(sock, sendbuf, strlen(sendbuf));
                read(sock, recvbuf, strlen(recvbuf));
                fputs(recvbuf, stdout);

                memset(sendbuf, 0, sizeof(sendbuf));
                memset(recvbuf, 0, sizeof(recvbuf));
        }
        
	    close(sock);

        return 0;
}


猜你喜欢

转载自blog.csdn.net/weixin_37413035/article/details/82708952