Socket编程小结

龙云尧个人博客,转载请注明出处。

CSDN地址:http://blog.csdn.net/Michael753951/article/details/72990141

个人blog地址:http://yaoyl.cn/socketbian-cheng-xiao-jie/

本部分内容是对之前博客【 Ubuntu下进行Socket编程】中的补充说明,在这篇博客中,我将详细解释Socket编程的代码,分析编程的流程和思想。

声明:本部分代码分别引用自【Linux C Socket编程原理及简单实例】以及【Linux网络编程:socket文件传输范例】。

首先我们先分析第一份较为简单的代码,使用Socket编程完成一个两个控制台窗口之间通信的demo。

简单通信

在分析之前,我推荐你先看一下这篇博客【简单理解Socket】,上面很详细的解释说明了Socket编程的过程中,服务器端和客户端分别需要调用的函数和调用流程。比如博客中下面这张图,很重要也很直观的。

接下来我们开始分析代码,看代码的过程中,我们需要不断回头看看上面这张流程图,方便对整个代码进行理解剖析。

服务器端代码如下。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MYPORT 8887
#define QUEUE 20
#define BUFFER_SIZE 1024

int main()
{
    ///获得sockfd
    // SOCK_STREAM 表明使用TCP协议(有序,流)
    // 参考地址 http://baike.baidu.com/link?url=msvzh3jij6QHrqSMUVsst9P7o0wCNRJFS2qwZ_G5uu0hBL090wOB-4Nfgv7ye2-ZlQ3ZnnR4tqueYg6ThnqXSNP6aRe371EX2dhl5uYZ_jm
    int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);

    ///定义sockaddr_in,用来表示接受方的IP地址
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET; // 设置地址家族
    // htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处。 参考地址:http://baike.baidu.com/link?url=QUJ7h2uCP-Fag7xTlSgVCsZyGFr-hqJuaO-L5wRHbH7ODChk-vsHwsiipqVqIiJeNf1dCz4aeJ7SEvC-324Hfa
    // 绑定端口号至8887号
    server_sockaddr.sin_port = htons(MYPORT); // 设置端口
    // htonl将主机数转换成无符号长整型的网络字节顺序。本函数将一个32位数从主机字节顺序转换成网络字节顺序。
    // INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。 参考地址:http://baike.baidu.com/link?url=UnkEXjpR6yhwsqWJ9d_77IzqsbEPaiO_e8WI355TORvFwpgf9zO4sQcBLilHuKxTYEOanDCWxGedtp_8-7BfVDZwyjN8ZDJDLrXitPwr49Bf5E7OwvEKHQj6EjZQHGcPU8DpDDsdeixUmYnZxgJMya
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置地址

    ///bind,成功返回0,出错返回-1
    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
    {
        perror("bind");
        exit(1);
    }

    ///listen,成功返回0,出错返回-1
    // 消息队列长度为20
    if(listen(server_sockfd, QUEUE) == -1)
    {
        perror("listen");
        exit(1);
    }

    ///客户端套接字
    char buffer[BUFFER_SIZE];
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);

    ///成功返回非负描述字,出错返回-1
    int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
    if(conn<0)
    {
        perror("connect");
        exit(1);
    }

    while(1)
    {
        memset(buffer,0,sizeof(buffer));
        int len = recv(conn, buffer, sizeof(buffer),0);
        if(strcmp(buffer,"exit\n")==0)
            break;
        fputs(buffer, stdout);
        send(conn, buffer, len, 0);
    }
    close(conn);
    close(server_sockfd);
    return 0;
}

仔细查看代码,各种参数的设置原因我已经写在注释中了,就不再赘述。稍加注意我们便可以发现服务器端代码中,依次调用了socket, bind, listen, accept, recv, close。整个流程是符合我们之前放出来的那张图的,我们在计算机网络中也学习过,5层网络模型中,socket套接字担任了应用层和网络层之间通信的任务,我们也知道了每个socket套接字会绑定一个特定的端口,从而接受这个端口传入的讯息。

详细分析

代码中,首先使用socket函数定义消息使用IPv4网络,建立一个面向连接的稳定数据传输,即TCP传输。

然后作者定义了一个sockaddr_in结构体,用来存放服务器的信息。sockaddr_in中存放了客户端的网络通信方式——IPv4,TCP,以及监听的ip地址。(这里监听的0.0.0.0,表示监听本机上所有的ip地址,这里的ip地址是指5层网络中的第4层网络层的ip地址,即服务器端自身的ip地址,这部分资料可以参考【关于socket绑定INADDR_ANY】)。上面有比较详细的解释。

接下来调用bind函数绑定socket,同时调用listen开始监听,使用accept接收消息队列中的消息内容,监听的ip和端口号已经在sockaddr_in中已经定义好了。

接着使用一个while循环忙等待,等待调用recv让服务端收到从其他客户端发来的消息。send表示将消息发送出去。

只要将服务端的代码看懂,类似的,我们也能够很简单的看懂客户端的代码内容。请自行对着那张流程图和代码进行比对阅读。

demo2分析

仔细查看demo2使用TCP传输文件的例子,其实和上一个demo没什么本质上的区别,流程的关键代码都一致。不过稍微注意的是,在这一份demo中多了一行代码。

setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

关于这一行代码的解释,我在这里就不多说了,因为我也并没有很看明白,如果你有兴趣的话,可以参考这篇博客,上面有很详尽的解释以及说明。【setsockopt()用法(参数详细说明)】

到这里其实想不出还有什么需要在解释的了,代码很简单,因为这两份demo都很纯粹,简单几行代码便能够展示清楚使用TCP变成的操作方法和顺序(如果你想使用UDP编程的话,方法类似)。

你可能感兴趣的:(c++,Ubuntu,socket编程)