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;

posted @ 2019-08-23 00:57  LandyTan  阅读(325)  评论(0编辑  收藏  举报