Linux下的socket编程

同一台计算机上的进程可以通过IPC(进程间通信)机制进行通信;而不同机算计上运行的进程则通过网络IPC,即套接字(socket)进行通信。Linux下的socket API是基于BSD套接口而是实现的,通过这些统一的API就可以轻松实现进程间的网络通信。此外,socket API即可用于面向连接(TCP)的数据传输,又可用于无连接(UDP)的数据传输。一般使用Client/Server交互模型进行通信。

本文以及下文将实现一个面向连接的C/S通信模型。本文首先介绍服务器端的实现。

1.创建套接字

1#include < sys/socket.h >
2int socket(int domain, int type, int protocol);

通过socket函数可以创建一个套接字描述符,这个描述符类似文件描述符。通过这个套接字描述符就可以对服务器进行各种相关操作。

该函数包含三个参数,domain参数用于指定所创建套接字的协议类型。通常选用AF_INET,表示使用IPv4的TCP/IP协议;如果只在本机内进行进程间通信,则可以使用AF_UNIX。参数type用来指定套接字的类型,SOCK_STREAM用于创建一个TCP流的套接字,SOCK_DGRAM用于创建UDP数据报套接字。参数protocol通常取0。对于本文所描述的服务器,创建套接字的示例代码如下:

1if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
2 
3    printf("socket error!\n");
4    exit(1);
5}

2.绑定套接字

对于服务器而言,它的IP地址和端口号一般是固定的。服务器的IP即为本地IP,而服务器的端口号则需要显示的指定。通过bind函数可将服务器套接字和一个指定的端口号进行绑定。

在具体介绍绑定函数之前,先说明一下socket中的套接字地址结构。由于套接字是通过IP地址和端口号来唯一确定的,因此socket提供了一种通用的套接字地址结构:

1struct sockaddr {
2            sa_family_t sa_family;
3            char        sa_data[14];
4        }

sa_family指定了套接字对应的协议类型,如果使用TCP/IP协议则改制为AF_INET;sa_data则用来存储具体的套接字地址。不过在实际应用中,每个具体的协议族都有自己的协议地址格式。比如TCP/IP协议组对应的套接字地址结构体为:

01struct sockaddr_in {
02    short int sin_family; /* Address family */
03    unsigned short int sin_port; /* Port number */
04    struct in_addr sin_addr; /* Internet address */
05    unsigned char sin_zero[8]; /* Same size as struct sockaddr */
06};
07 
08struct in_addr {
09    unsigned long s_addr;
10};

该地址结构和sockaddr结构均为16字节,因此通常在编写基于TCP/IP协议的网络程序时,使用sockaddr_in来设置具体地址,然后再通过强制类型转换为sockaddr类型。

绑定函数的函数原型如下:

1#include < sys/socket.h >
2int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数sockfd即服务器的套接字描述符;addr参数指定了将socket绑定到的本地地址;addrlen则为所使用的地址结构的长度。示例代码如下

01memset(&my_addr, 0, sizeof(struct sockaddr_in));
02my_addr.sin_family = AF_INET;
03my_addr.sin_port = htons(SERV_PORT);
04my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
05 
06if(bind(sockfd, (struct sockaddr *)&my_addr,
07            sizeof(struct sockaddr_in)) == -1) {
08 
09    printf("bind error!\n");
10    exit(1);
11}

注意在上述代码中,将IP地址设置为INADDR_ANY,这样就既适合单网卡的计算机又适合多网卡的计算机。

3.在套接字上监听

对于C/S模型来说,通常是客户端主动的对服务器端发送连接请求,服务器接收到请求后再具体进行处理。服务器只有调用了listen函数才能宣告自己可以接受客户端的连接请求,也就是说,服务器此时处于被动监听状态。listen函数的原型如下:

1#include < sys/socket.h >
2int listen(int sockfd, int backlog);

sockfd为服务器端的套接字描述符,backlog指定了该服务器所能连接客户端的最大数目。超过这个连接书目后,服务器将拒绝接受客户端的连接请求。示例代码如下:

1    #define BACKLOG 10
2if(listen(sockfd, BACKLOG) == -1) {
3 
4    printf("listen error!\n");
5    exit(1);
6}

4.接受连接

listen函数只是将服务器套接字设置为等待客户端连接请求的状态,真正接受客户端连接请求的是accept函数:

1#include < sys/socket.h >
2 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数中所使用的参数都在上面的API中有所描述。accept函数执行成功后将返回一个代表客户端的套接字描述符,服务器进程通过该套接字描述符就可以与客户端进行数据交换。

5.数据传输

由于socket适用多个数据传输协议,则不同的协议就对应不同的数据传输函数。与TCP协议对应的发送数据和接受数据的函数如下:

1#include < sys/socket.h >
2#include < sys/types.h >
3ssize_t send(int sockfd, const void *buf, size_t len, int flags);
4ssize_t recv(int sockfd, void *buf, size_t len, int flags);

从这两个函数的原型可以看书,socket中的数据传输函数与普通文件的读写函数类似,只不过第一个参数需要传递套接字描述符;buf指定数据缓冲区;len为所传输数据的长度;flag一般取0。示例代码如下:

01while (1) {
02    sin_size = sizeof(struct sockaddr_in);
03    if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
04        printf("accept error!\n");
05        continue;
06    }
07    /*
08             *进行相应的数据处理;
09             */
10}

如示例代码所示,通过while循环使得服务器对客户端进行持续监听。如果客户端有连接请求则新建一个代表客户端的套接字描述符,进而进行对客户端数据的接受和发送。

上述的几个函数属于网络编程中最基本的也是最关键的几个API,依次通过上述的方法就可以完成服务器端的程序的编写,具体的过程还可以参考下图:


正如上图所示,在处理完客户端的数据传输请求后,必须通过close函数关闭客户端的连接。



上文中对面向连接的C/S模型中的服务器进行了简单描述,本文将说明如何编写一个客户端应用程序。与服务器程序不同,客户端程序要求有友好的交互界面,因此本文所示的客户端程序采用Qt和linux C共同完成。客户端的界面部分采用Qt完成,而与服务器间具体的通信部分则通过linux C语言完成。

1.界面设计

通过Qt Designer可快速设计好客户端界面,并对四个按钮所对应的信号和槽函数进行连接。客户端所对应的类为ClientUI


2.与服务器间通信的编程

由于Qt是对C++的一种扩展,因此我们必须将使用Linux C编写的客户端通信程序封装成类,这里我们将其定义为ClientConnection。

我们已经对客户端的界面和通信部分分别定义了两个类,但是如何将两者结合在一起?方法很简单。将ClientConnection类的对象cli作为ClientUI类的成员,然后在具体的槽函数中对应相应的通信函数。和上文分析服务器编程的方法不同,本文将以分析客户端对应的槽函数的方式来说明如何编写客户端程序。

2.1连接按钮的槽函数

在连接按钮对应的槽函数中,首先创建客户端套接字描述符;然后从输入框中获得服务器的IP地址;再通过connect函数创建一个连接。

connect函数的原型如下:

1#include < sys/socket.h >
2int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect函数用于在sockfd套接字上创建一个连接,sockfd即客户端套接字;addr即为服务器端的套接字地址;addrlen为addr的长度。我们将connect函数封装成ClientConnection类中的connectingSocket函数,该成员函数在连接按钮所对应的槽函数中被调用。:

1int ClientConnection::connectingSocket()
2{
3    if (::connect(sockfd, (struct sockaddr *)&serv_addr,
4                          sizeof(struct sockaddr)) == -1) {
5        return 0;
6    }
7 
8    return 1;
9}

在该槽函数中,可以这样使用连接函数:

1if (cli.connectingSocket() == 1) {
2    ui->statusLabel->setText("connecting to server success!");
3} else {
4    ui->statusLabel->setText("ERROR:connecting to server fail!");
5    //exit(1);
6}

当客户端连接至服务器成功后,客户端就可以跟服务器端进行数据的传送。

2.2 修改按钮的槽函数

该槽函数的工作比较简单,使得文本编辑区可被编辑。

2.3 应用按钮的槽函数

在该槽函数中,将文本编辑区的数据发送至服务器端;或从服务器接受数据显示在这个文本编辑区中。发送和接受数据的API仍然是send和recv函数,不过这里我们将这两个函数封装成ClientConnection类中的sendData成员和recvData成员:

01int ClientConnection::recvData(char *buf)
02{
03    if ((recvbytes = recv(sockfd, buf, MAX_DATA_NUM, 0)) == -1) {
04        return 0;
05    }
06 
07    buf[recvbytes] = '\0';
08    return 1;
09}
10 
11int ClientConnection::sendData(char *msg, int len)
12{
13    if (send(sockfd, msg, len, 0) == -1) {
14        printf("send error!\n");
15        return 0;
16    }
17 
18    return 1;
19}

这两个成员函数在该槽函数的适当位置被调用。

2.4退出按钮的槽函数

退出按钮的槽函数中只需断开客户端和服务器之间的连接即可。

-------------------------------------------------本文转载自网络----------------------------------------------------------------------

http://edsionte.com/techblog/archives/2746


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值