首页 > 代码库 > Linux的TCP基础编程

Linux的TCP基础编程

网络地址数据结构问题

首先,先来说网络中的编程地址,不知有没有人发现在我们网络编程中不仅仅只有一个地址数据结构,而且很多时候我们在调用网络接口的时候还要强制转换参数的类型。对,我说的就是数据结构sockaddr和sockaddr_in这两货,而如果你足够仔细的话,你会发现,编程中我们使用的大部分是sockaddr_in,但是我们调用的网络接口却几乎都是(不知道有没有不是的啊,我木有去调查)sockaddr类型的,为什么?下面先来看看这两种数据结构的定义,如下所示:

/**
 *  通用网络地址结构
 */
struct sockaddr
{
    __uint8_t	sa_len;         /* 01 byte total length */
    sa_family_t	sa_family;      /* 02 byte [XSI] address family */
    char		sa_data[14];	/* 14 byte [XSI] addr value (actually larger) */
};

/**
 * Socket address, internet style.
 * 我们一般使用的网络地址
 * 这个是IP4类型的,定义于头文件<netinet in="" h="">中
 * IP6类型的有对应的数据结构,切大小和IP4的不一样,这里就不说了  
 */
struct sockaddr_in
{
    __uint8_t	sin_len;
    sa_family_t	sin_family;
    
    in_port_t	sin_port;       /* 02 byte 端口    */
    struct	in_addr sin_addr;   /* 04 byte IP地址  */
    char		sin_zero[8];    /* 08 byte 保留字段 */
};

从上述的代码的定义以及注释我们可以发现,sockaddr是通用的地址数据结构,最后的14个字节是保留的,而sockaddr_in是使用了sockaddr_in的后面的14字节的一种结构,两个数据结构的大小一样,只是sockaddr_in定义了sockaddr未定义的一些字节而已。这样做我想是因为想让系统平台自己决定后面的14个字节的使用方式吧,但是又给出了统一的接口,上述的实现是XSI实现的版本。

因此,我们平时声明sockaddr_in这样类型的地址结构,然后初始化该结果,最后转换为sockaddr这样类型的参数传到函数里面去使用。


TCP网络通信过程

服务器:TCP服务器端首先需要使用socket函数创建一个socket_server,然后把已经初始化的服务器地址信息使用bind函数绑定到socket_server。绑定成功后,服务器端使用listen进入监听状态,然后服务器调用accept函数进入等待客户端连接的状态,此时程序会在调用acept的地方阻塞,直到有客户端请求连接的时候,才会使得程序继续执行。此时服务器端可以使用函数accept返回的与该该客户端通信的socket_client与客户端通信,如果accept的地址参数不为空,还可以获取到客户端的地址信息。与客户端通信的可以使用read和write函数,当需要结束与该客户端的通信的时候,可以使用close关闭socket_client,断开与客户端的连接。最后程序退出的时候,别忘记把之前的监听套接字socket_server给close掉。


客户端:客户端相对于服务器端要简单点,没有所谓的监听和接受连接请求,但是有请求连接部分,下面来说说客户端是如何与服务器通信的。首先,客户端使用socket创建一个通信套接字,然偶使用服务器的地址和端口信息初始化通信地址结构,接着使用创建成功的套接字以及初始化的通信地址信息通过connect函数请求与服务器连接,如果连接成功,那么此后就可以使用该套接字和服务器通信了,也可以使用read和write函数获取和发送消息,当不需要通信的时候,客户端调用close关闭套接字,断开与服务器的连接。

其实,有个流程图还是很简单的,如下所示:

技术分享

主要网络编程函数

  • socket
    int socket(int domain, int type, int protocol);
    根据指定的地址族、数据类型和协议来分配一个socket的描述字及其所用的资源。
    domain   :  协议族。     常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址
    type         :  socket类型。 常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
    protocol  :  协议。       吃常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等

  • bind
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    把一个地址族中的特定地址信息和socket绑定
    sockfd    :  socket描述字,也就是socket引用
    addr       :  要绑定给sockfd的协议地址
    addrlen :  地址的长度
    通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

  • listen
    int listen(int sockfd, int backlog);
    服务器启动监听socket
    sockfd      :  要监听的socket描述字
    backlog   :  相应socket可以排队的最大连接个数 

  • accept
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    TCP服务器监听到客户端请求之后,调用accept()函数处理客户端的连接请求,成功后返回与该客户端通信的套接字
    sockfd     :  服务器的socket描述字
    addr        :  客户端的socket地址
    addrlen  :  socket地址的长度

  • connect
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    客户端根据服务器地址信息请求连接服务器
    sockfd   :  客户端的socket描述字
    addr      :  服务器的socket地址
    addrln  :  socket地址的长度

  • read
    ssize_t read(int fd, void *buf, size_t count);
    读取socket对应的可用的内容,即接收通信信息
    fd        : socket描述字
    buf      :缓冲区
    count  :缓冲区长度

  • write
    ssize_t write(int fd, const void *buf, size_t count);
    向socket写入内容,其实就是发送内容
    fd         :socket描述字
    buf       :缓冲区
    count   :缓冲区长度

  • close
    int close(int fd);
    关闭套接字fd,结束当前的TCP连接

代码示例

服务器端

//
//  main.cpp
//  TCPServer
//
//  Created by God Lin on 15/1/14.
//  Copyright (c) 2015年 arbboter. All rights reserved.
//

#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netdb.h>      /* sockaddr_in 声明 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h> /* inet_ntoa声明 */

void start_server(unsigned int port);

int main(int argc, const char * argv[])
{
    start_server(22221);
    return 0;
}

void start_server(unsigned int port)
{
    int fd_server = socket(AF_INET, SOCK_STREAM, 0);
    
    // set address
    sockaddr_in addr_server;
    memset(&addr_server, 0, sizeof(addr_server));
    addr_server.sin_family      = AF_INET;
    addr_server.sin_addr.s_addr = htonl(INADDR_ANY);
    addr_server.sin_port        = htons(port);
    
    // socket bind with address
    if(bind(fd_server, (struct sockaddr*)&addr_server, sizeof(struct sockaddr)) == -1)
    {
        printf("bind socket failed\n");
        return;
    }
    
    // server socket start list, waitting client to connect
    // 这个client_max是指同时连接数
    if(listen(fd_server, 10) == -1)
    {
        printf("start listen socket failed\n");
        return;
    }
    
    int fd_client      = -1;
    sockaddr_in addr_client;
    socklen_t addr_len = 0;
    int nConnect       = 10;
    while (--nConnect>0)
    {
        // 必须设置长度,否则失败的!
        addr_len = sizeof(struct sockaddr_in);
        // 商量好先读取客户端发送的信息,然后然后返回给客户端一个随机数字
        
        fd_client = accept(fd_server, (struct sockaddr*)&addr_client, &addr_len);
        if(fd_client == -1)
        {
            printf("accept socket failed\n");
            return;
        }
        
        // create a thread to talk with client
        printf("recived connection from [%s] : ", inet_ntoa(addr_client.sin_addr));
        
        // 读取消息
        char buf[512] = {0};
        read(fd_client, buf, sizeof(buf));
        printf("%s\n", buf);
        
        // 返回消息
        sprintf(buf, "magic number is : %d", rand()%1000);
        write(fd_client, buf, strlen(buf));
        
        // 关闭本次通信
        close(fd_client);
    }
    close(fd_server);
}

客户端代码

//
//  main.cpp
//  TCPServer
//
//  Created by God Lin on 15/1/14.
//  Copyright (c) 2015年 arbboter. All rights reserved.
//
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <string.h>
#include <arpa/inet.h>
#include <string.h>

void start_client(const char* server_addr, int port);

int main()
{
    start_client("127.0.0.1", 22221);
    return 0;
}

void start_client(const char* server_addr, int port)
{
    int sockfd_server;
    struct sockaddr_in addr_server;
    
    // create socket
    sockfd_server = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd_server == -1)
    {
        printf("init socket failed\n");
        return;
    }
    
    // set server address
    memset(&addr_server, 0, sizeof(addr_server));
    addr_server.sin_family = AF_INET;
    addr_server.sin_addr.s_addr = inet_addr(server_addr);;
    addr_server.sin_port = htons(port);
    
    // connect server
    if(connect(sockfd_server,(struct sockaddr *)&addr_server,sizeof(struct sockaddr))==-1)
    {
        printf("connect server failed\n");
        return;
    }
    
    char buf[512] = {0};
    sprintf(buf, "Hi, i'm hello!");
    
    write(sockfd_server, buf, strlen(buf));
    
    // 获取服务器返回信息
    size_t nRead = read(sockfd_server, buf, sizeof(buf));
    buf[nRead] = '\0';
    printf("recived from server : %s", buf);
    
    close(sockfd_server);
}



Linux的TCP基础编程