zoukankan      html  css  js  c++  java
  • Linux Socket编程

      最近搭建了一个Linux服务器做FTP网盘,突发奇想写写Linux程序(不然岂不浪费了一台电脑???),因为是Linux程序,肯定是作为服务器端运行啦,服务器程序怎么离得开Socket咧?所以呢,先在Linux下写个简单的ECHO小试鸡刀(/捂脸)。

      

      其实Linux Socket编程跟Windows差不多,最底层还是socket、bind、listen、accept/connect这些函数。不一样的大概就四个地方:

    1、头文件——在Windows下,所有Socket函数都包含在WinSock.h下,而在Linux下就不一样了,socket函数在<sys/types.h>和<sys/socket.h>下,close函数则在<unistd.h>下。不同的函数可能包含在众多不同的头文件下(实在是记不住啊,还好Linux下有man,不然真得撞墙了)。

    2、动态链接库——众所周知在Windows下写网络程序,都需要链接Ws2_32.dll。而在Linux下不需要。

    3、关闭socket对象函数——在Windows下SOCKET对象用完之后要调用closesocket将其释放掉,而在Linux下同样也需要释放,不同的是调用的函数不一样,Linux下调用close函数。

    4、套接字结构体——在Windows下,socket返回的是SOCKET,而在Linux下,socket返回的是int。其实查看Windows头文件可以看到SOCKET这个类型其实就是转定义类型。其本质上是int。

      服务端/客户端代码流程与Windows是一致的:

    服务器端程序基本流程:创建服务端套接字-->绑定套接字-->监听套接字-->接收客户端连接请求-->收/发消息-->释放客户端套接字对象-->释放服务端套接字对象。

    客户端程序基本流程:创建服务端套接字-->连接服务端-->收/发消息-->释放套接字对象。

      以下为程序代码

    // Server.cpp

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <iostream>
    #include <string.h>
    #include <string>
    #include <unistd.h>
    
    int main()
    {
        try{
            // 创建服务端套接字
            int so = socket(AF_INET, SOCK_STREAM, 0);
            if(so == -1) throw "Create socket error.";
    
            // 绑定服务端套接字到指定的IP与Port上
            sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(9089);
            addr.sin_addr.s_addr = INADDR_ANY;
            if(bind(so, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) throw "绑定套截字失败";
    
            // 监听套接字
            if(listen(so, 5) == -1) throw "监听套接字失败";
            std::cout << "Start server finish ..." << std::endl;
        
            // 客户端信息
            int soClient;
            int nClientAddSize;
            sockaddr_in addrClient;
            while(true)
            {
                // 初始化客户端信息存储地址
                soClient = 0;
                nClientAddSize = sizeof(sockaddr_in);
                memset(&addrClient, 0, sizeof(sockaddr_in));
    
                // 接收客户端的连接请求
                soClient = accept(so,  (sockaddr*)&addrClient, (socklen_t*)&nClientAddSize);
                if(soClient == -1)continue;
    
                // 接收客户端发送过来的数据
                const unsigned short cunRecvArraySize = 1024;
                char szRecv[cunRecvArraySize];
                memset(szRecv, 0, sizeof(char) * cunRecvArraySize);
                int nRecvSize = recv(soClient, szRecv, cunRecvArraySize * sizeof(char), 0);
                std::cout << szRecv << std::endl;
    
                // 发送数据给客户端
                if(send(soClient, szRecv, nRecvSize, 0) != nRecvSize)
                    std::cout << "Send message to client error." << std::endl;
    
                // 关闭客户端的连接,释放客户端套接字对象
                close(soClient);
            }
            // 释放服务端的套接字对象
            close(so);
        }catch(const char *pMsg)
        {
            std::cout << pMsg << std::endl;
        }
        return 0;
    }

    // Client.cpp

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    
    #include <iostream>
    #include <string.h>
    
    
    int main()
    {
    	try{
    		int so = socket(AF_INET, SOCK_STREAM, 0);
    		if(so == -1)throw "Create socket error.";
    
    		sockaddr_in addr;
    		memset(&addr, 0, sizeof(addr));
    		addr.sin_port = htons(9089);
    		addr.sin_family = AF_INET;
    		addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    		if(connect(so, (sockaddr*)&addr, sizeof(addr)) == -1)throw "Connect server error.";
    		std::cout << "Send:" << "Hello" << std::endl;
    		if(send(so, "Hello", strlen("Hello"), 0) != strlen("Hello"))throw "Send message to server error.";
    		char szRecv[512];
    		memset(szRecv, 0, 512);
    		int nRecv = recv(so,  szRecv, 512, 0);
    		if(nRecv < 1)throw "Recv message to server error.";
    		std::cout << "Recv:" << szRecv << std::endl;
    		close(so);
    	}catch(const char *pMsg)
    	{
    		std::cout << pMsg << std::endl;
    	}
    
    	return 0;
    }

    随便写写,不然漫漫长夜怎么过(/捂脸)

    顺便再解释下各个函数的参数及返回值吧!

    int socket( int af, int type, int protocol);

    af:一个地址描述。目前仅支持AF_INET格式

    type:套接字的类型。常用的为流式套接字和数据报套接字。流式套接字即为TCP协议,面向连接的可靠的;数据报呢则为UDP协议,无连接不可靠的。

    protocol:套接字使用的协议。常用的协议有IPPROTO_TCP和IPPROTO_UDP等、当然也可以不显式指定使用何种协议,如果不显式指定则传入0即可。

    返回值:没有错误发生的情况,返回一个新的套接字。如果发生错误,则返回-1。

    示例:int so = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);

    sockfd:被绑定的套接字。即我们上面调用socket function返回的套接字对象。

    my_addr: 这是一个结构体,这个结构体存储了套接字所使用的端口号、地址及协议。后面再详细讲这个结构体

    addrlen:就是上面这个my_addr结构体的大小。

    返回值:没有错误发生返回0,否则返回-1。

    示例:

    sockaddr_in addr;

    // TODO:给addr赋值

    bind(so, (sockaddr*)&addr, sizeof(addr);

    int listen(int fd, int backlog);

    fd:被监听的套接字。即我们上面调用socket function返回的套接字对象。

    backlog:连接请求队列最大长度。一般传5即可。

    返回值:没有错误发生返回0,否则返回-1。

    示例:listen(so, 5);

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    sockfd:从哪个套接字接收客户端的连接,一般为我们上面调用socket function返回的套接字对象。

    addr:客户端地址信息。

    addrlen:客户端地址信息的大小。注意这里传入的是指针,且指针的值不能为0.

    返回值:没有错误发生的情况,返回一个新的客户端套接字。后续与客户端通信就用这个套接字对象。如果发生错误,则返回-1。

    示例:sockaddr_in addrClient; int nClientAddrSize = sizeof(sockaddr_in);

    int soClient = accept(so, (sockaddr*)&addr, &nClientAddrSize);

     int connect(int s, const struct sockaddr * name, int namelen);

    s:需要连接服务端的套接字对象。即我们上面调用socket function返回的套接字对象。

    name:服务端的地址信息

    namelen:服务端地址信息的大小

    返回值:没有错误发生返回0,否则返回-1。

    示例:connect(so, (sockaddr*)&addr, sizeof(sockaddr_in));

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);

    sockfd:接收数据的套接字。你要给某人发短信,那你总得告诉运营商你要给谁发短信吧。

    buf:要发送的信息。

    len:信息长度。

    flags:这个好像是优先级。我们一般传入0即可。

    返回值:没有错误发生返回实际发送的大小,否则返回-1.

    示例:send(so, "hello", strlen("hello"), 0);

    int recv(int s, void* buf, int len, int flags)

    s:要接收信息的套接字。

    buf:接收到的信息存放的缓存区

    len:缓存区大小

    flags:这个好像是优先级。我们一般传入0即可。

    返回值:没有错误发生返回实际接收的大小,否则返回-1 or 0。

    示例:int nRecv = recv(soClient, pRecvBuffer, cunRecvBufferSize, 0);

    int close(int fd)

    fd:要关闭的套接字对象
    返回值:没有错误发生返回0,否则返回-1。

    示例:close(soClient);

    函数就是以上这些了。下面我们来聊聊sockaddr_in结构体

    sockaddr_in 定义如下:

    struct sockaddr_in
    {
    short sin_family;
    unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
    struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
    unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
    };

    sin_family:一个地址描述。必须与socket function第一个参数相同。

    sin_port:端口号。要注意的是这个端口号使用的是网络字节顺序,而不是我们通常使用的主机字节顺序。所有设定这个值得时候需要转换。转换函数有htons、htonl主机字节转网络字节,htons转换成整型网络字节顺序,htonl转换成无符号长整型的网络字节顺序。一般我们使用htons即可。

    sin_addr:地址。与端口号一致,都是使用的网络字节顺序。可以使用inet_addr function将点分十进制的IP地址转换成长整型网络字节顺序。相反网络字节顺序转点分十进制的function是 inet_ntoa 。如果指定固定地址的话,可以传入INADDR_ANY,表示所有地址。

    sin_zero:不需要。

    示例:

    sockaddr_in addr;

    addr.sin_family = AF_INET;

    addr.sin_port = htons(8080);

    addr.sin_addr.s_addr = INADDR_ANY;

  • 相关阅读:
    GBDT(MART)
    C#中数组中Skip、Take和Concat的用法
    VUE中对获取到的数组进行排序
    el-date-picker只能选择今天
    Vue获取时间
    执行Add-Migration Initial报错
    Vue中使用for循环绑定值
    Element UI——日期时间选择器el-date-picker开始时间与结束时间约束解决方案
    el-date-picker日期组件
    缓存的问题
  • 原文地址:https://www.cnblogs.com/LandyTan/p/11397659.html
Copyright © 2011-2022 走看看