博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
socket网络编程之TCP、UDP
阅读量:6139 次
发布时间:2019-06-21

本文共 11152 字,大约阅读时间需要 37 分钟。

之前说的用于进程间通信的几种方式:消息signal、管道pipe、消息队列msg、共享内存shm、信号量sem。都只适用于一台主机上的进程间通信,那么如何实现两台计算机之间的进程通信呢?所以,来了解一下异地进程通信

1 异地进程通信

  • 协议层为双方的主机通信进程分配“端口”和缓冲区,以便异地进程间的通信。

    1.1TCP/IP协议

    以下是OSI参考模型与TCP/IP参考模型的对应关系:

    socket网络编程之TCP、UDP
    socket网络编程之TCP、UDP

    1.1.1 TCP/IP协议族

    TCP/IP 协议组大体上分为三部分:

    1.Internet 协议(IP)
    2.传输控制协议(TCP)和用户数据报文协议(UDP)
    3.处于TCP 和UDP 之上的一组协议专门开发的应用程序。它们包括:TELNET,文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP)等许多协议。
    应用层协议

  • Telnet
  • 文件传送协议(FTP和TFTP)
  • 简单的文件传送协议(SMTP)
  • 域名服务(DNS)等协议

    2 网络编程基础

  • socket标准被扩展成window socket和unix socket
  • linux中的网络编程通过socket接口实现。
  • Socket既是一种特殊的IO,它也是一种文件描述符
  • 一个完整的Socket 都有一个相关描述{协议,本地地址,本地端口,远程地址,远程端口};每一个Socket 有一个本地的唯一Socket 号,由操作系统分配。

    2.1 SOCKET分类

    流式套接字(SOCK_STREAM)

    流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
    数据报套接字(SOCK_DGRAM)
    数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。
    原始套接字。
    原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。

    2.2 编程流程

    TCP

    socket网络编程之TCP、UDP
    UDP
    socket网络编程之TCP、UDP
    具体函数的用法,就自己man了。

    2.2.1 套接字地址结构

    重点讲一下套接字地址结构:

    #include < netinet/in.h>struct sockaddr{unsigned short sa_family; /* address族, AF_xxx */char sa_data[14];     /* 14 bytes的协议地址 */};
  • sa_family的取值,一般来说,IPV4使用“AF_INET”
  • sa_data包含了一些远程电脑的地址、端口和套接字的数目,里面的数据是杂溶在一起的。一般我们不用这个结构体,因为我们一般使用的地址都是IP+端口号。比如:IP192.168.159.2 port3306 。这样来记录地址。所以一般使用下面这个地址结构,而知数据类型是等效的,可以互相转换。
    #include < netinet/in.h>struct sockaddr_in {short int sin_family; /* Internet地址族 */unsigned short int sin_port; /* 端口号 */struct in_addr sin_addr; /* Internet地址 */unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/};

    2.2.2 字节序列转换

  • 因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,而有的系统是底位在前,高位在后 ),而网络传输的数据大家是一定要统一顺序的。所以对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换。
  • htons()——“Host to Network Short”
    主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)
  • htonl()——“Host to Network Long” 
    主机字节顺序转换为网络字节顺序(对无符号长型进行操作4bytes)
  • ntohs()——“Network to Host Short”
    网络字节顺序转换为主机字节顺序(对无符号短型进行操作2bytes)
  • ntohl()——“Network to Host Long ”
    网络字节顺序转换为主机字节顺序(对无符号长型进行操作4bytes)

    2.2.3地址格式转换

    -linux提供将点分格式的地址转于长整型数之间的转换函数。

  • inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。
  • inet_ntoa()能够把网络字节顺序转换为地址结构的数据。

    2.2.4基本套接字调用

    socket() bind() connect()

    listen() accept() send()
    recv() sendto() shutdown()
    recvfrom() close() getsockopt()
    setsockopt() getpeername()
    getsockname() gethostbyname()
    gethostbyaddr() getprotobyname()
    fcntl()

    练习1-TCP

    TCP连接,等待客户端输入,将内容发送给服务器,并获取客户端地址。

    这里,getsocketname()表示获得本地(自己)的地址;
    getpeername()表示获得连接上的客户端的地址(源IP地址)。
    <br>
    server.c

    #include < sys/types.h>     #include < sys/socket.h>#include < netinet/in.h>    //sockaddr_in#include < stdio.h>#include < string.h>int main(){    int fd;    int clientfd;    int ret;    pid_t pid;    int addrLen = 0;    char acbuf[20] = "";    struct sockaddr_in addr = {0};  //自己的地址    struct sockaddr_in clientAddr = {0};    //连上的客户端的地址    //1.socket()    fd = socket(PF_INET,SOCK_STREAM,0);    if(fd == -1)    {        perror("socket");        return -1;    }    //2.bind()    addr.sin_family = AF_INET;    addr.sin_port = htons(1234);    addr.sin_addr.s_addr = inet_addr("192.168.159.6");    ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));    if(ret == -1)    {        perror("bind");        return -1;    }    //3.listen()    ret = listen(fd,10);    if(ret == -1)    {        perror("listen");        return -1;    }    //4.阻塞 等待 accept()    clientfd = accept(fd,NULL,NULL);    if(clientfd == -1)    {        perror("accept");        return -1;    }//获取客户端地址addrLen = sizeof(struct sockaddr_in);ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);if(ret == -1){    perror("getpeername");    return -1;}printf("client login.\nip: %s , port: %d\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));//5.通信while(1){    memset(acbuf,0,20);    if (read(clientfd,acbuf,20) > 0)    {        printf("receive: %s\n",acbuf);    }}//6.close()close(fd);return 0;}

client.c

#include 
#include
#include
//sockaddr_in#include
#include
int main(){ int fd; int ret; char acbuf[20] = ""; struct sockaddr_in serAddr = {0}; //1.socket(); fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //2.连接connect() 服务器的地址 serAddr.sin_family = AF_INET; serAddr.sin_port = htons(1234); serAddr.sin_addr.s_addr = inet_addr("192.168.159.6"); ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in)); if(ret == -1) { perror("connect"); return -1; } //3.通信 while(1) { printf("send: "); fflush(stdout); scanf("%s",acbuf); if(strcmp(acbuf,"exit") == 0) { break; } write(fd,acbuf,strlen(acbuf)); } //4.close() close(fd); return 0;}

运行结果:

socket网络编程之TCP、UDP
做个改进,以上代码,只能一个客户端连接上。因为TCP是基于点对点的,一个accept()对应一个connnect()。要想连接多个客户端,就得使用fork(),一个进程用来专门阻塞等待客户端的连接,一个用来处理与已连接上客户端的通信。
代码如下:

server.c

int main()    {        int fd;        int clientfd;        int ret;        pid_t pid;        int addrLen = 0;        char acbuf[20] = "";        char client_addr[100] = "";        struct sockaddr_in addr = {0};  //自己的地址        struct sockaddr_in clientAddr = {0};    //连上的客户端的地址        signal(SIGCHILD,SIG_IGN);        //1.socket()        fd = socket(PF_INET,SOCK_STREAM,0);        if(fd == -1)        {            perror("socket");            return -1;        }        //会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use .        //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟        //if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)            //{        //      perror("setsockopet error\n");        //      return -1;            //}        //2.bind()        addr.sin_family = AF_INET;        addr.sin_port = htons(1234);        addr.sin_addr.s_addr = inet_addr("192.168.159.6");        ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));        if(ret == -1)        {            perror("bind");            return -1;        }        //3.listen()        ret = listen(fd,10);        if(ret == -1)        {            perror("listen");            return -1;        }        while(1)                {            //4.阻塞等待 accept()            clientfd = accept(fd,NULL,NULL);            if(clientfd == -1)            {                perror("accept");                return -1;            }            pid = fork();   //父进程负责继续监听等待,子进程父子与已连接客户端通信            if(pid == -1)            {                perror("fork");                return -1;            }            if(pid == 0)    //子进程            {                //获取客户端地址                addrLen = sizeof(struct sockaddr_in);                ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);                if(ret == -1)                {                    perror("getpeername");                    return -1;                }                sprintf(client_addr,"ip: %s , port: %d\n",\                    inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));                printf("client longin.\n%s\n",client_addr);                //5.通信                while(1)                {                    memset(acbuf,0,20);                    if (read(clientfd,acbuf,20) == 0)   //客户端退出                    {                        //结束相应的server进程                        close(clientfd);                        exit(0);    //僵尸进程                    }                    printf("from %sreceive : %s\n\n",client_addr,acbuf);                }            }            else    //父进程            {                //返回while,继续等待            }        }        //6.close()        close(fd);        return 0;    }

这里一定要注意,每结束一个客户端,一定要关掉相应的文件描述符,并且结束掉子进程(僵尸进程),不然,随着客户端的增加,进程数会越来越大

client.c

int main(){    int fd;    int ret;    int addrLen;    char acbuf[20] = "";    struct sockaddr_in serAddr = {0};    struct sockaddr_in myAddr = {0};    //1.socket();    fd = socket(PF_INET,SOCK_STREAM,0);    if(fd == -1)    {        perror("socket");        return -1;    }    //2.连接connect() 服务器的地址    serAddr.sin_family = AF_INET;    serAddr.sin_port = htons(1234);    serAddr.sin_addr.s_addr = inet_addr("192.168.159.6");    ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));    if(ret == -1)    {        perror("connect");        return -1;    }    //获取自己的地址    addrLen = sizeof(struct sockaddr_in);    ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);    if(ret == -1)    {        perror("getsockname");        return -1;    }    printf("client---ip: %s , port: %d\n",\                inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));    //3.通信    while(1)    {        printf("send: ");        fflush(stdout);        scanf("%s",acbuf);        if(strcmp(acbuf,"exit") == 0)        {            break;        }        write(fd,acbuf,strlen(acbuf));    }    //4.close()    close(fd);    return 0;}

运行结果:

socket网络编程之TCP、UDP

练习2-UDP

使用UDP连接,完成上述内容。但是发现,使用UDP,因为是面向无连接的,所以在没有收到或者发送包之前,是无法得知源IP地址的。

那UDP如何知道客户端的IP地址和端口呢?
1、由客户端显示地高速服务器IP地址和端口,发消息。
2、隐式的。服务器从收到的包头中得到源IP和端口号。
server.c

int main(){    int sockfd;    int ret;    char acbuf[20] = "";    char client_addr[100] = "";    struct sockaddr_in addr = {0};    struct sockaddr_in clientAddr = {0};    int addrLen = sizeof(struct sockaddr_in);    int reuse = 0;    //1.socket()    sockfd = socket(PF_INET,SOCK_DGRAM,0);    if(sockfd == -1)    {        perror("socket");        return -1;    }    //2.bind()    addr.sin_family = AF_INET;    addr.sin_port = htons(1235);    addr.sin_addr.s_addr = inet_addr("127.0.0.1");    ret = bind(sockfd,(struct sockaddr *)&addr,addrLen);    if(ret == -1)    {        perror("bind");        return -1;    }    //3.通信    while(1)    {        memset(acbuf,0,20);        if(recvfrom(sockfd, acbuf, 100,0,(struct sockaddr *)&clientAddr,&addrLen) == -1)        {             perror("recvfrom");             return -1;         }          //收到客户端的数据包之后,就可以知道客户端地址        sprintf(client_addr," ip: %s , port: %d\n",\                inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));         printf("receive from %s: %s\n",client_addr,acbuf);    }    //4.close    close(sockfd);    return 0;}

client.c

int main(){    int sockfd;    int ret;    int addrLen = sizeof(struct sockaddr_in);    char acbuf[20] = "";    struct sockaddr_in serAddr = {0};    struct sockaddr_in myAddr = {0};    //1.socket()    sockfd = socket(PF_INET,SOCK_DGRAM,0);    if(sockfd == -1)    {        perror("socket");        return -1;    }    serAddr.sin_family = AF_INET;    serAddr.sin_port = htons(1235);    serAddr.sin_addr.s_addr = inet_addr("127.0.0.1");    //2.通信    while(1)    {        printf("send: ");        fflush(stdout);        scanf("%s",acbuf);        if(strcmp(acbuf,"exit") == 0)        {            break;        }        sendto(sockfd, acbuf, 20,0,(struct sockaddr *)&serAddr,addrLen);        //获取自己的地址        ret = getsockname(sockfd,(struct sockaddr *)&myAddr,&addrLen);        if(ret == -1)        {            perror("getsockname");            return -1;        }        printf("client---ip: %s , port: %d\n\n",\                    inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));    }    //3.close    close(sockfd);    return 0;}

运行结果:

socket网络编程之TCP、UDP
会发现,此时是可以直接运行多个客户端的,因为,UDP是面向无连接的,可以是一对多,多对一,多对多的,只要客户端知道服务器地址,就可以连上。
<br>
Ps:本人理解有限,还未学习完,有错请指出。

转载于:https://blog.51cto.com/13097817/2053955

你可能感兴趣的文章
表达式与运算符
查看>>
jquery使用js的一些疼处
查看>>
django中视图处理请求方式(FBV、CBV)
查看>>
IOC的概念及原理
查看>>
iOS页面传值方式
查看>>
WebSocket简单使用
查看>>
java学习笔记-集合
查看>>
linux下载相关
查看>>
OC-socket使用介绍
查看>>
CCF NOI1154 大整数开方
查看>>
51Nod-1018 排序【排序】
查看>>
CCF NOI1024 因子个数
查看>>
POJ1700 Crossing River
查看>>
那些年薪百万的程序员“咸鱼翻身”没有透露的秘密
查看>>
python2.#与python3.#下tkinter 的simpledialog,messagebox
查看>>
何为.Net Remoting
查看>>
已经搞ACM半年多了没有当时的热情了,最近看了一点退役贴觉得是应该为自己写点什么了...
查看>>
利用图片对文件进行加密与解密
查看>>
《剑指Offer》题十一~题二十
查看>>
二分K-均值算法
查看>>