通过TCP协议进行C/S模式的网络通信

通过TCP协议进行C/S模式的网络通信

  • server.c

1.sockaddr_in 结构体

    struct sockaddr_in{
        sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
        uint16_t        sin_port;     //16位的端口号
        struct in_addr  sin_addr;     //32位IP地址
        char            sin_zero[8];  //不使用,一般用0填充
    };

in_addr 结构体

    struct in_addr{
        in_addr_t  s_addr;  //32位的IP地址
    };

in_addr_t 在头文件 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换.

By the way, ntohs() 本函数将一个16位数由网络字节顺序转换为主机字节顺序

sockaddr 结构体的定义如下:

    struct sockaddr{
        sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
        char         sa_data[14];  //IP地址和端口号
    };

这里我们使用 sockaddr_in 结构体,然后再强制转换为 sockaddr 类型

2…在 Linux 下使用 头文件中 socket() 函数来创建套接字,原型为:

int socket(int pf, int type, int protocol);

pf 是“Protocol Family”的简写,也就是 IP 协议类型,常用的有PF_INETPF_INET6

af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INETAF_INET6。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6

type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)

protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。在pf和type都只有一种协议满足条件,可以将 protocol 的值设为 0,系统会自动推演出应该使用什么协议。如下:

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

使用 socket() 函数创建套接字以后,返回值就是一个 int 类型的文件描述符

POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 unistd.h。

3.bind() 函数的原型为:

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  //Linux

sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。

4.通过 listen() 函数可以让套接字进入被动监听状态,它的原型为:

int listen(int sock, int backlog);  //Linux

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。

所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

5.当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);  //Linux

它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

**accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。**后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

6.从服务器端发送数据使用 send() 函数,它的原型为:

int send(SOCKET sock, const char *buf, int len, int flags);

最后的 flags 参数一般设置为 0 或 NULL,初学者不必深究。

7.在客户端接收数据使用 recv() 函数,它的原型为:

int recv(SOCKET sock, char *buf, int len, int flags);
#include
#include
#include
int main()
{
    int sockfd = -1;
    int ret = -1;
    int newfd = -1;
    struct sockaddr_in seraddr;

    //创建sockaddr_in结构体变量
    memset(&seraddr, 0, sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(1235);
    seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    memset(seraddr.sin_zero, 0, 8);

    //创建套接字
    sockfd =  socket(PF_INET, SOCK_STREAM, 0);

    //将套接字和IP、端口绑定
    ret = bind(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
    if (-1 == ret)
    {
        printf("bind err!");
        close(sockfd);
        return -1;
    }

   //进入监听状态,等待用户发起请求
    ret = listen(sockfd, 1024);
    if (-1 == ret)
    {
        printf("listen err!");
        close(sockfd);
        return -1;
    }
    
    //接收客户端请求
    struct sockaddr_in clntaddr;
    socklen_t clntaddr_size = sizeof(clntaddr);
    newfd = accept(sockfd, (struct sockaddr*)&clntaddr, &clntaddr_size);

    //接收客户端请求发送的数据
    char szBuf[1024] = "\0";
    char szReplyBuf[1024] = "hello, it's server!\0";
    ret = recv(newfd, szBuf, sizeof(szBuf), 0);
    if (ret > 0)
    {
        printf("recv \"%s\" from %s:%d\n",
                szBuf,
                (char*)inet_ntoa(clntaddr.sin_addr),
                ntohs(clntaddr.sin_port));
    }
    
   //向客户端发送数据
    ret = send(newfd, szReplyBuf, strlen(szReplyBuf), 0);
     if (ret > 0)
    {
         printf("send \"%s\" to %s:%d\n",
	           szReplyBuf,
	           (char*)inet_ntoa(clntaddr.sin_addr),
	           ntohs(clntaddr.sin_port));
    }

    //关闭套接字
    close(sockfd);
    close(newfd);

    return 0;
 }
  • client.c
    8.connect() 函数用来建立连接,它的原型为:
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  //Linux
#include
#include
#include
int main()
{
    int sockfd = -1;
    int ret = -1;
    struct sockaddr_in seraddr;
    struct sockaddr_in clntaddr;
   
    //创建sockaddr_in结构体变量
    memset(&seraddr, 0, sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    seraddr.sin_port = htons(1235);
    memset(seraddr.sin_zero, 0, 8);

   //创建套接字
    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    //向服务器(特定的IP和端口)发起请求
    ret = connect(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr));

    if (-1 == ret)
    {
        printf("connect err!\n");
        close(sockfd);
        return -1;
    }

    char szBuf[1024] = "\0";
    char szMsg[1024] = "Hi, it's client!";
    
   //向服务器发送数据
    ret = send(sockfd, szMsg, strlen(szMsg), 0);
    if (ret > 0)
    {
        printf("send \"%s\" to %s:%d\n", 
                szMsg,
                (char*)inet_ntoa(seraddr.sin_addr),
                ntohs(seraddr.sin_port));
    }

    //读取服务器传回的数据
    ret = recv(sockfd, szBuf, sizeof(szBuf), 0);
    if (ret > 0)
    {
        printf("recv \"%s\" from %s:%d\n",
                szBuf,
                (char*)inet_ntoa(seraddr.sin_addr),
                ntohs(seraddr.sin_port));
    }

   //关闭套接字
    close(sockfd);
    return 0;
}
  • 实验结果

通过TCP协议进行C/S模式的网络通信_第1张图片

  • 参考资料[ http://c.biancheng.net/view/2344.html ]

你可能感兴趣的:(庖丁解牛Linux网络核心)