15.tcp协议和socket编程


15.1.linux网络编程框架
(1)网络是分层的,OSI制定了7层模型;网络为什么要分层,因为网络通信非常复杂;网络分层的具体表现为4层;TCP/IP协议是用的最多的网络协议实现;TCP/IP分为4层,对应OSI的7层;我们编程时最关注应用层,了解传输层(tcp+udp+tftp等),网际互联层和网络接入层不用管。
(2)BS和CS架构=CS架构(client_server,客户端服务器架构,譬如QQ客户端和QQ服务器)+BS架构(broswer_server,浏览器服务器架构,后续会成为主流架构)。


15.2.TCP协议简介
(1)TCP协议工作在传输层(APP->TCP协议->IP协议),对上服务socket接口(APP的API接口),对下调用IP层;TCP协议面向连接,通信前必须先3次握手建立连接关系后才能开始通信;TCP协议提供可靠传输,不怕丢包/乱序等;TCP的接收方收到数据包后会ack给发送方,若发送方未收到ack会丢包重传;TCP的有效数据内容会附带校验码,以防止内容在传递过程中损坏;TCP会根据网络带宽来自动调节适配速率(滑动窗口技术);发送方会分割报文并进行编号,接收方会校验编号,一旦顺序错误即会重传。
(2)TCP建立连接需要三次握手,建立连接的条件是服务器出于listen状态时客户端主动发起connect连接;关闭连接需要四次握手,服务器或者客户端都可以主动发起关闭(握手协议已经封装在TCP协议内部,socket编程接口平时不用管);TCP连接必须要服务端先listen,然后客服端这边主动发起connect,然后客户端发送SYN,服务器回复SYN+ACK,然后客户端再回复SYN,双方建立连接,建立连接后双方可自由通信,通信完后服务器和客户端都可以发送FIN报文,接收方回复ACK,然后接收方发送FIN报文,然后对方回复ACK,双方关闭连接,整个通信结束;参考链接http://blog.csdn.net/whuslei/article/details/6667471


15.3.TCP通信模式和应用
(1)基于TCP通信的服务模式;服务器必须具有公网IP地址(或者使用动态IP地址映射技术);服务器端socket->bind->listen后处于监听状态;客户端socket后,直接connect去发起连接;服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,而且双方均可发起;双方均可发起关闭连接。
(2)常见的使用了TCP协议的网络应用;http和ftp(应用层协议,主要使用在浏览器中)在此处可将其看做是http应用程序(超文本信息的传送如图像等)和ftp应用程序(纯文本信息的传送),这两个应用程序在底层都广泛使用了TCP协议;QQ客户端和QQ服务之间的通信也是通过TCP协议连接进行通讯的;mail客户端和mail服务器之间使用http协议,底层也是通过TCP协议的。


15.4.socket编程接口介绍
(1)建立连接;socket(类似于open,用来打开1个网络连接,如果成功则返回1个网络文件描述符(int类型),之后我们操作该网络连接都通过该网络文件描述符);bind(把本地IP地址和申请到的Socket绑定起来);listen(服务器监听端口队列);connect(客户端调用connect连接服务器)。
(2)发送和接收;send和write(写数据信息到Socket中);recv和read(从Socket中读数据信息)。
(3)辅助性函数(进行IP地址的转换,由点分十进制格式(p/a->”192.168.1.1”)转换成二进制格式(n));inet_aton和inet_addr和inet_ntoa(不支持IPv6);inet_ntop、inet_pton(目前推荐使用的接口,支持IPv6和IPv4)。
(4)表示IP地址相关数据结构都定义在/usr/include/netinet/in.h中;struct_sockaddr(网络编程接口中用来表示1个IP地址,注意该IP地址是不区分IPv4和IPv6,或者说是兼容IPv4和IPv6);typedef-uint32_t-in_addr_t(网络内部用来表示IP地址的类型);struct-in_addr{in_addr_t-s_addr;};(针对in_addr_t的第1层封装->in_addr);struct-sockaddr_in{略}(IPv4的IP地址结构体);struct-sockaddr(linux的网络编程接口中用来表示IP地址的标准结构体,bind和connect等函数中都需要该结构体,此结构体是兼容IPV4和IPV6的,在实际编程中该结构体会被1个struct-sockaddr_in(IPv4的IP地址)或1个struct-sockaddr_in6(IPv6的IP地址)所填充)(见图1)。


15.5.soekct实践编程
(1)端口号,实质就是1个数字编号,用来在我们1台主机中(主机的操作系统中)唯一的标识1个能上网的进程;端口号和IP地址一起会被打包到当前进程发出或者接收到的每个数据包中;每个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在1起不分家的。
(2)服务器端程序编写流程=socket(使用socket打开1个文件描述符)+bind(使用bind和本地绑定,即将该服务器程序的端口号+IP地址和该socket进行绑定)+listen(监听端口)+accept(返回值是1个fd,accept正确返回就表示我们已经和前来连接我的客户端之间建立了1个TCP连接了,以后我们就要通过该连接来和客户端进行读写操作,读写操作就需要1个fd,该fd就由accept来返回了)。
(3)客户端程序编写流程=socket(使用socket打开1个文件描述符)+connect(和服务器建立连接);socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,是用来和连接那端的客户端程序进行读写。
(4)如何让服务器和客户端好好沟通;客户端和服务器原则上都可以任意的发和收,但是实际上双方必须配合,client发的时候server就收,而server发的时候client就收;client和server之间的通信本质上来说是异步的,这就是问题的根源;解决方案是依靠应用层协议来解决,说白了就是我们server和client事先做好一系列的通信约定。
(5)自定义应用层协议(TCP连接建立后通过应用层协议来实现业务逻辑);首先规定服务器和客户端之间的发送和接收方法,规定连接建立后由客户端主动向服务器发出1个请求数据包,然后服务器收到数据包后回复客户端1个回应数据包,这就是1个通信回合,整个连接的通信就是由N多个回合组成的;然后定义数据包格式,一般是定义某个标准结构体进行数据的封装;常用的应用层协议(有http/ftp等)和自定义应用层协议原理相同,只是通信机制更加健全和复杂而已。


这里写图片描述


15.ip_address_translation.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:待定
 * 功能:演示inet_addr和inet_pton和inet_ntop函数的使用。
 * 0x66     01      a8      c0
 * 0d102    001     168     192
 * 网络字节序即大端模式,网络中只允许有大端模式,不允许有小端模式。
 */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>

#define IPADDR "192.168.1.102" 

int main(int argc, char **argv)
{
#if 1
    const char *ret = NULL;
    in_addr_t addr = 0x6601a8c0;
    char buf[50] = {0};

    ret = inet_ntop(AF_INET, &addr, buf, sizeof(buf));
    if (NULL == ret)
    {
        perror("inet_ntop error");
        exit(-1);
    }

    printf("addr = %s.\n", buf);            // addr = 192.168.1.102.
#endif

#if 0
    struct in_addr addr = {0};
    int ret = -1;

    ret = inet_pton(AF_INET, IPADDR, &addr);
    if (1 != ret)
    {
        printf("inet_pton error.\n");
        exit(-1);
    }

    printf("addr = 0x%x.\n", addr.s_addr);  // addr = 0x6601a8c0.
#endif  

#if 0
    in_addr_t addr = 0;

    addr = inet_addr(IPADDR);

    printf("addr = 0x%x.\n", addr);         // addr = 0x6601a8c0.
#endif
    return 0;
}

15.socket/datapack.h
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:tcp协议和socket编程
 * 功能:封装数据包。
 */

#define CMD_REGISTER    1001    // 注册学生信息
#define CMD_CHECK       1002    // 检验学生信息
#define CMD_GETINFO     1003    // 获取学生信息

#define STAT_OK         30      // 回复ok
#define STAT_ERR        31      // 回复出错了

typedef struct commu
{
    char name[20];              // 学生姓名
    int age;                    // 学生年龄
    int cmd;                    // 命令码
    int stat;                   // 状态信息,用来回复
}info;
15.socket/Makefile
all:
    gcc server_socket.c -o ser
    gcc client_socket.c -o cli

clean:
    rm ser cli *.o -rf
15.socket/server_socket.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:tcp协议和socket编程
 * 功能:演示简单的socket服务器程序。
 */
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include "datapack.h"

#define SERPORT     9003
#define SERADDR     "192.168.2.51"          // 在linux下通过ifconfig命令查看
#define BACKLOG     100                     // 客户端排队数

char databuf[100];                          // 数据缓冲区
info student;                               // 学生信息数据包

int main(int argc, char **argv)
{
    int sockfd = -1, clifd = -1, ret = -1;
    socklen_t len = 0;
    struct sockaddr_in seraddr = {0};
    struct sockaddr_in cliaddr = {0};

    // 调用socket打开文件描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 地址族为IPv4;流式socket,使用TCP协议
    if (-1 == sockfd)
    {
        perror("sockfd error");
        exit(-1);
    }
    printf("socket success, the sockfd = %d.\n", sockfd);

    // bind绑定sockefd和当前电脑的ip地址&端口号
    seraddr.sin_family = AF_INET;                   // 设置地址族为IPv4
    seraddr.sin_port = htons(SERPORT);              // 设置地址的端口号信息
    seraddr.sin_addr.s_addr = inet_addr(SERADDR);   // 设置IP地址
    ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
    if (-1 == ret)
    {
        perror("bind error");
        exit(-1);
    }
    printf("bind success.\n");

    // listen监听端口
    ret = listen(sockfd, BACKLOG); // 阻塞等待客户端来连接服务器
    if (-1 == ret)
    {
        perror("listen error");
        exit(-1);
    }
    printf("listen success.\n");

    // accept阻塞等待客户端接入
    clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
    if (-1 == clifd)
    {
        perror("accept error");
        exit(-1);
    }
    printf("connect success, the client fd = %d.\n", clifd);

#if 0
    // 服务器反复接收客户端发过来的数据
    while (1)
    {
        ret = recv(clifd, &databuf, sizeof(databuf), 0);
        if (-1 == ret)
        {
            perror("recv error");
            exit(-1);
        }

        printf("The number of bytes recv is %d.\n", ret);
        printf("The monent of recv is %s.\n", databuf);

        memset(databuf, 0, sizeof(databuf));
    }
#endif

#if 0
    // 服务器反复给客户端发数据
    while (1)
    {
        printf("please input the coment of send data.\n");
        gets(databuf);

        ret = send(clifd, &databuf, strlen(databuf), 0);
        if (-1 == ret)
        {
            perror("send error");
            exit(-1);
        }

        printf("The number of bytes send is %d.\n", ret);
        printf("The monent of send is %s.\n", databuf);

        memset(databuf, 0, sizeof(databuf));
    }
#endif

#if 1
    // TCP连接建立后通过应用层协议来实现业务逻辑
    while (1)
    {
        // 服务器接收客户端发送的请求
        ret = recv(clifd, &student, sizeof(info), 0);
        if (-1 == ret)
        {
            perror("recv error");
            exit(-1);
        }

        // 服务器解析客户端数据包,然后处理业务
        if (student.cmd == CMD_REGISTER)
        {
            // 服务器要进行真正的注册动作,通常是插入数据库1条信息
            printf("The user will to register the news of student.\n");
            printf("The name of student is %s.\n", student.name);
            printf("The age of student is %d.\n", student.age);

            // 服务器回复客户端数据表明请求已经处理OK
            student.stat = STAT_OK;
            ret = send(clifd, &student, sizeof(info), 0);
            if (-1 == ret)
            {
                perror("send error");
                exit(-1);
            }
        }
    }
#endif
    return 0;
}
15.socket/client_socket.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:tcp协议和socket编程
 * 功能:演示简单的socket客户端程序。
 */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include "datapack.h"

#define SERADDR     "192.168.2.51"          // 服务器开放给我们的IP地址
#define SERPORT     9003                    // 服务器开放给我们的端口号

char databuf[100];                          // 数据缓冲区
info student;                               // 学生信息数据包

int main(int argc, char **argv)
{
    int sockfd = -1, ret = -1;
    struct sockaddr_in seraddr = {0};
    struct sockaddr_in cliaddr = {0};

    // 调用socket打开文件描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 地址族为IPv4;流式socket,使用TCP协议
    if (-1 == sockfd)
    {
        perror("sockfd error");
        exit(-1);
    }
    printf("socket success, the sockfd = %d.\n", sockfd);

    // 调用connect链接服务器
    seraddr.sin_family = AF_INET;                   // 设置地址族为IPv4
    seraddr.sin_port = htons(SERPORT);              // 设置地址的端口号信息
    seraddr.sin_addr.s_addr = inet_addr(SERADDR);   // 设置IP地址
    ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
    if (-1 == ret)
    {
        perror("connect error");
        exit(-1);
    }
    printf("connect success, the fd is = %d.\n", ret);

#if 0
    // 客户端反复给服务器发数据
    while (1)
    {
        printf("please input the coment of send data.\n");
        gets(databuf);

        ret = send(sockfd, &databuf, strlen(databuf), 0);
        if (-1 == ret)
        {
            perror("send error");
            exit(-1);
        } 

        printf("The number of bytes send is %d.\n", ret);
        printf("The monent of send is %s.\n", databuf);

        memset(databuf, 0, sizeof(databuf));
    }
#endif  

#if 0
    // 客户端反复接收服务器数据
    while (1)
    {
        ret = recv(sockfd, &databuf, sizeof(databuf), 0);
        if (-1 == ret)
        {
            perror("recv error");
            exit(-1);
        }

        printf("The number of bytes recv is %d.\n", ret);
        printf("The monent of recv is %s.\n", databuf);

        memset(databuf, 0, sizeof(databuf));
    }
#endif

#if 1
    // TCP连接建立后通过应用层协议来实现业务逻辑
    while (1)
    {
        // 客户端发送请求到服务器
        printf("please input the name.\n");
        scanf("%s", student.name);
        printf("please input the age.\n");
        scanf("%d", &student.age);
        student.cmd = CMD_REGISTER;
        ret = send(sockfd, &student, sizeof(student), 0);
        if (-1 == ret)
        {
            perror("send error");
            exit(-1);
        }

        // 客户端接收服务器的回复
        memset(&student, 0, sizeof(student));
        ret = recv(sockfd, &student, sizeof(student), 0);
        if (-1 == ret)
        {
            perror("recv error");
            exit(-1);
        }

        // 客户端解析服务器的回复,再做下1步定夺
        if (student.stat == STAT_OK)
        {
            printf("register news success.\n");
        }
        else
        {
            printf("register news error.\n");
        }
    }
#endif
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值