TCP网络通信

1.TCP通信架构

对于服务器,其通信流程一般有如下步骤:

    1.调用socket函数创建socket,这一步会创建一个文件描述符fd。
    2.调用bind函数将socket(也就是fd)绑定到某个ip和端口的二元组上。
    3.调用listen函数开启侦听端口。
    4.调用accept阻塞等待接受连接,当有客户端请求连接上来后,

产生一个新的socket(客户端socket)。
    5.基于新产生的socket调用send或recv函数开始与客户端进行数据通信。
    6.通信结束后,调用close函数关闭客户端socket。
对于客户端,其通信流程一般有如下步骤:

    1.调用socket函数创建客户端socket。
    2.调用connect函数尝试连接服务器。
    3.连接成功以后调用send或recv函数开始与服务器进行数据通信。
    4.通信结束后,调用close函数关闭socket。

其流程图如下所示:

服务器端代码:

#include <stdio.h>     
#include <sys/types.h>          
#include <sys/socket.h>
 
#include <netinet/in.h>
#include <netinet/ip.h>
 
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <pthread.h>
#include <stdlib.h>
 
 
#include "common.h"
 
struct thread_msg {
    int cli_socket;
    pthread_t tid;
    
};
 
 
void *serv_for_client(void *args)
{
    int ret;
    struct thread_msg *pmsg = args;
    int socket = pmsg->cli_socket;
    
    struct msg_struct serv_msg;
    while(1){
        memset(&serv_msg,0,sizeof(serv_msg));
        ret = recv(socket,&serv_msg,sizeof(serv_msg),0);
        if(ret<0){
            perror("recv err"); close(socket); free(pmsg); return NULL;
        }else if(ret == 0){
            printf("serv found peer shutdwon\n"); close(socket);  free(pmsg);
            return NULL;
        }
        
        printf("serv got msg:temp%d %d %d cnt%d des:%s\n",\
            serv_msg.temp,serv_msg.humidity,serv_msg.wind,\
                serv_msg.sendcnt,serv_msg.des);        
        serv_msg.serv_response++;
    
        ret = send(socket,&serv_msg,sizeof(serv_msg),0);
        if(ret<0){
            perror("send err");  return NULL;
        }
    }
    close(socket);
    return NULL;
}
 
 
int main()
{
    /*
        
       #include <sys/types.h>          
       #include <sys/socket.h>
       int socket(int domain, int type, int protocol);
            domain:  域,范围,区域
                AF_UNIX,: linux进程内部通信
                AF_INET ,用于ipv4通信
                AF_INET6,用于ipv6通信
            type:
                SOCK_STREAM, TCP专属,提供面向连接 字节流 安全可靠服务
                SOCK_DGRAM,   UDP专属
                SOCK_RAW ,   自己实现一套接口
            protocol: 为0即可
            
            返回值:
                正数--新的连接socket
                -1:  errno
                
    */
    int socket_main = socket(AF_INET,SOCK_STREAM, 0 );
    if(socket_main <0){
        perror("create main socket err");
        return -3;
    }
    
    /*
        给main socket指定服务器自己的ip和端口号
       #include <sys/types.h>          
       #include <sys/socket.h>
       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
        addr:应该包含ip和端口号
            我们没有使用这个类型
                struct sockaddr {
                   sa_family_t sa_family;        //地址族,采用AF_xxx的形式,如:AF_INET
                   
                   char        sa_data[14];        //14字节的特定协议地址
               }
               
            编程中一般不针对 sockaddr  数据结构操作,而是使用与 sockaddr 等价的sockaddr_in 数据结构
               
            我们使用的是下面的类型:
            
               struct sockaddr_in {        man  7 ip
                   sa_family_t    sin_family;     永远等于AF_INET.   address family: AF_INET 
                   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
                   
                   struct in_addr sin_addr;     internet address  IP地址
               };
               Internet address.  
               struct in_addr {
                   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
               };
        
            addrlen: addr结构体长度
            返回值:
                0-success 
                -1: errno
    
    */
    struct sockaddr_in  serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(10086);  //host to net short
    
    /* int inet_addr(char *ip)
        负责将字符串表示的 点分法,转换为 u32,以大端方式存放
    */
    
    serv_addr.sin_addr.s_addr = INADDR_ANY;      //inet_addr("192.168.3.197");   将a.b.c.d形式的IP转换为32位的IP
                                            //将一个点分十进制的IP转换成一个长整型数(u_long类型)等同于inet_addr()。
    /*    如果你指定ip地址,改程序只能跑在指定服务器上,   如何让服务器自动获取本地ip
        serv_addr.sin_addr.s_addr = 0;  bind函数发现ip为0,自动获取本地ip    
    */
    int ret =  bind(socket_main,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    if(ret<0){
        perror("bind err");
        return -34;
    }
    
    /*监听整个网络
    
       #include <sys/types.h>          
       #include <sys/socket.h>
       int listen(int sockfd, int backlog);
            backlog:  主socket在一瞬间可以同时处理的客户端个数
                    一般选5,表示可以同时处理2*5+1
            返回:
                0-success 
                -1:errno
    */
    ret = listen(socket_main,5);
    if(ret<0){
        perror("listen err");
        return -3;
    }
    
    /*
    
    
        等待客户端连接-----阻塞等待
        
       #include <sys/types.h>         
       #include <sys/socket.h>
       int accept(int main_sockfd, struct sockaddr *addr, socklen_t *addrlen);
            main_sockfd:主socket,用于监听网络,等待客户端连接 
            addr:  客户端连接上来之后,客户端的ip+port存放在addr 
                真实的类型是 struct sockaddr_in
            
                      struct sockaddr_in {        man  7 ip
                           sa_family_t    sin_family;     永远等于AF_INET.   address family: AF_INET 
                           in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
                           struct in_addr sin_addr;     internet address  
                       };
                       Internet address.  
                       struct in_addr {
                           uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
                       };
            addrlen:  双向参数  ,  对方地址的长度
                    调用者(你)告诉accept存放地址的buf有多长.
                    accept获取对方地址之后,会告诉真实的长度
            返回值:
                正数: 一个新的socket,用于和客户端进行通信
                -1:errno
    */
    struct sockaddr_in cli_addr;
    socklen_t addrlen=sizeof(cli_addr);
    
    struct thread_msg *pmsg  =NULL;
    
    int socket_new;
loop:
    socket_new = accept(socket_main ,(struct sockaddr *)&cli_addr, &addrlen  );
    if(socket_new<0){
        perror("accept err");
        return -3;
    }
    
    printf("serv accept client ip=%s port=%d\n",\
            inet_ntoa (cli_addr.sin_addr  ),   ntohs(cli_addr.sin_port)  );
                // inet_ntoa ,将32位IP转换为a.b.c.d的格式
        
    
    pmsg = malloc(sizeof(*pmsg));
    pmsg->cli_socket = socket_new;
    pthread_create(&pmsg->tid,NULL,   serv_for_client   , pmsg   );
 
    //处理完毕一个连接,然后继续去等待新客户端
    goto loop;
 
    close(socket_main);  //一般最后才能关闭
    return 0;
}
 
 
 客服端代码

#include <stdio.h>     
#include <sys/types.h>          
#include <sys/socket.h>
 
#include <netinet/in.h>
#include <netinet/ip.h>
 
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
 
#include "common.h"
 
int main()
{
    int socket_cli = socket(AF_INET,SOCK_STREAM, 0 );
    if(socket_cli <0){
        perror("create client socket err");
        return -3;
    }
    
    /*
    
        //int connect(int socket_cli, serv_ip,serv_port);
           #include <sys/types.h>          
       #include <sys/socket.h>
         int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
                struct sockaddr_in {
                   sa_family_t    sin_family;     永远等于AF_INET.   address family: AF_INET 
                   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
                   struct in_addr sin_addr;     internet address  
               };
               Internet address.  
               struct in_addr {
                   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
               };
            成功返回 0
            失败 -1,errno 
        
    */
    struct sockaddr_in  serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(10086); 
    serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197"); 
    int ret = connect(socket_cli,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    if(ret <0){
        perror("connect err");
        return -3;
    } 
    
    struct msg_struct cli_msg;
    
    while(1){
        cli_msg.temp=45;
        cli_msg.humidity=40;
        cli_msg.wind=10;
        strcpy(cli_msg.des,"today sun day");
        
        ret = send(socket_cli,&cli_msg,sizeof(cli_msg),0);
        if(ret<0){
            perror("send err");
            return -34;
        }
 
 
        memset(&cli_msg,0,sizeof(cli_msg));
        ret = recv(socket_cli,&cli_msg,sizeof(cli_msg),0);
        if(ret<0){
            perror("recv err");
            return -34;
        }else if(ret ==0){
            printf("cli found serv shutdown\n");
            return 0;
        }
        
        printf("cli got msg:response=%d\n",cli_msg.serv_response);
         
        sleep(1);
    }
}

头文件:

#ifndef COMM_HH
#define COMM_HH
 
 
struct msg_struct {
    char temp;
    int humidity;
    int wind;
    
    int sendcnt;
    int serv_response;
    char des[32];
};
 
#endif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值