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);
}
时间: 2024-10-18 03:41:02

Linux的TCP基础编程的相关文章

Linux下TCP网络编程与基于Windows下C#socket编程间通信

一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使socket处于被动的监听模式,并为该  socket  建立一个输入数据队列,将到达的服务器, 请求保存在此队列中,直到程序处理他们. Accept():让服务器接收客户的连接请求. Connect():客户端使用connect函数来配置 socket并与远端服务器建立一个 TCP 连接. Clos

Linux下TCP网络编程与基于Windows下C#socket编程之间通信

一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使socket处于被动的监听模式,并为该  socket  建立一个输入 数据队列,将到达的服务器, 请求保存在此队列中,直到程序处理他们. Accept():让服务器接收客户的连接请求. Connect():客户端使用connect函数来配置 socket并与远端服务器建立一个 TCP 连接. Clo

Linux下C基础编程----写在Blog之前的话

找个2个星期的实习吧.各种坎坷.既然公司只是做广告.为毛让我去笔试!严重鄙视.想在也想开了,争取过了,结果顺其自然吧.还是好好写自己的学习心得吧,希望和我以前一样迷茫的各位同学,少走一点弯路.好了,切入正题: 一.参考书籍: 好的书籍的作用不用多说了.先得有基础,推荐以下基本书吧,个人觉得不错. <C和指针> <C专家编程> <C陷阱与缺陷> 上面三本书,认真看吧,特别是C和指针,可以当教材看,认真做做书后习题,我从这本书中受益菲浅 ,后两本用于提高,比如可以知道: c

大数据学习初体验:Linux学习+Shell基础编程+hadoop集群部署

距离上次博客时间已经9天,简单记录下这几天的学习过程 2020-02-15 10:38:47 一.Linux学习 关于Linux命令,我在之前就已经学过一部分了,所以这段时间的linux学习更多的是去学习Linux系统的安装以及相关配置多一些,命令会一些比较常用的就够了,下面记录下安装配置Linux系统时的注意事项. 这里配置的虚拟机的内存为4g 使用的 CentOS-6.5-x86_64-minimal.iso 映射文件 在进入linux系统中时,需要将虚拟机的主机名修改成自己想要的名字,还要

Linux的UDP基础编程

UDP通信机制 相比较于TCP通信,UDP通信是面向无连接的通信,所以没有TCP中的监听和连接等涉及面向连接的过程,UDP的主要通信过程如下图所示: 相比较TCP通信,UDP相对而言比较简单,虽然UDP是无连接的通信,但是依然有服务器和客户端之分,且通信的时候直接指定对方地址即可,无视对方是否能收到你发送的消息.且UDP通信不再使用read/write发送消息和读取消息了,因为没有连接,所以需要指定地址,而read和write没有能够提供地址的功能,所以需要使用其他方法替代.这里使用了sendt

Linux之V4L2基础编程【转】

转自:http://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html 本文内容来源于网络,本博客进行整理. 1. 定义 V4L2(Video For Linux Two) 是内核提供给应用程序访问音.视频驱动的统一接口. 2. 工作流程: 打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法(缓冲 区管理)-> 循环获取数据-> 关闭设备. 3. 设备的打开和关闭: #include <f

Linux 套接字编程 - TCP连接基础

第五章的内容,实现一个echo服务器和对应的客户端,主要收获: 0. TCP socket编程主要基本步骤 1. SIGCHLD信号含义(子进程退出时向父进程发送,提醒父进程对其状态信息进行一个获取),waitpid 和 wait在使用上的差异,前者可以配置参数设定为非阻塞方式调用,更加灵活. 2. 信号处理函数与其过程(尤其是信号发生后不列队这个性质),相同的信号多次发生(间隔非常接近的话)可能仅会调用一次信号处理函数 3. 信号处理对慢系统(阻塞)调用如accept等的影响(如果信号处理设置

快速学习C语言三: 开发环境, VIM配置, TCP基础,Linux开发基础,Socket开发基础

上次学了一些C开发相关的工具,这次再配置一下VIM,让开发过程更爽一些. 另外再学一些linux下网络开发的基础,好多人学C也是为了做网络开发. 开发环境 首先得有个Linux环境,有时候家里机器是Windows,装虚拟机也麻烦,所以还不如30块钱 买个腾讯云,用putty远程练上去写代码呢. 我一直都是putty+VIM在Linux下开发代码,好几年了,只要把putty和VIM配置好,其实 开发效率挺高的. 买好腾讯云后,装个Centos,会分配个外网IP,然后买个域名,在DNSPod解析过去

[Z] linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO

原文链接:http://blog.csdn.net/colzer/article/details/8169075 IO概念 Linux的内核将所有外部设备都可以看做一个文件来操作.那么我们对与外部设备的操作都可以看做对文件进行操作.我们对一个文件的读写,都通过调用内核提供的系统调用:内核给我们返回一个file descriptor(fd,文件描述符).而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符).描述符就是一个数字,指向内核中一个结构体(文件路径,数据