Linux下socket编程实现客户机服务器通信的例子

Linux下socket编程实现客户机服务器通信的例子

经典的在同一台主机上两个进程或线程之间的通信通过以下三种方法

  • 管道通信(Pipes)
  • 消息队列(Message queues)
  • 共享内存通信(Shared memory)

这里有许多其他的方法,但是上面三中是非常经典的进程间通信。

但是你有曾想过怎样跨越两台主机进行通信呢?

例如,当你浏览一个网站时,在你的主机上,运行的是你的浏览器,而在远程的系统上,运行的是网络服务器。因此这也是一个进程间的交流,他们彼此之间是通过套接字进行通信的,这也是本文的重点。


什么是Socket

从外行的角度来看,一个套接字是一个网络上的通信系统的终点。再精确的说就是套接字是一个系统的地址和端口的组合。因此,在每个系统中,存在一个套接字与套接字在网络上的其他系统交互。本地系统的socket和远程系统的socket的组合又称“四元”或“四元组(four tuple)”。两个进程运行在不同的系统间的连接可以通过他们的四元组唯一标识。

有两种类型的网络通信模型:

  • OSI
  • TCP/IP

而OSI是一个理论模型,TCP/IP网络模型才是最流行和用的最广的模型。

在我们的TCP/IP基础文章中解释说,在TCP/IP网络模型下发生的通信在形式上表现为客户端-服务器架构形式。即客户端开始通信和服务器建立一个连接。

套接字可以使用在许多语言中,如Java、C++等,但在这篇文章中,我们将了解在其在最纯净的形式下的套接字通信(C语言下的)。

让我们创建一个连续运行的服务器,并在客户端连接到它的时候发送日期和时间。


Socket Server Example

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h> 

int main(int argc, char *argv[])
{
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr; 

    char sendBuff[1025];
    time_t ticks; 

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000); 

    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 

    listen(listenfd, 10); 

    while(1)
    {
        connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 

        ticks = time(NULL);
        snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
        write(connfd, sendBuff, strlen(sendBuff)); 

        close(connfd);
        sleep(1);
     }
}

在上述代码中,我们创建了一个服务器,在代码中:

  • 在调用socket()的时候在内核创建了一个未命名的scoket并且返回了一个描述其的整数。
  • 此函数以域/family作为第一个参数,对于因特网中的ipv4的地址,我们取AF_INET。
  • 第二个参数”SOCK_STREAM”指定了传输层的协议,应该是很可靠并被大家所承认的技术,例如TCP。
  • 第三个参数一般是0,让内核决定默认的协议来进行连接。对于面向连接的可靠连接,默认的协议是TCP协议。
  • bind()函数用于将套接字与指定端口相连。在调用bind()函数的时候指定了特定的结构体serv_addr来创建socket的细节。具体的包括,family/域还有侦听的接口(在系统有多个网络接口的情况下)和服务器等待客户端请求的接口。
  • 在调用函数listen()的时候第二个参数’10’指定最大的客户端的连接数量,服务器将排队等待监听socket。
  • 在调用完listen()函数后,我们定义的socket就成为了全功能监听的socket。
  • 在调用函数accept()的时候,服务器就处于睡眠状态,当客户机发来一个请求并完成三次握手后accept()函数苏醒并且返回表示客户端socket的描述符。
  • accept()函数是用来接受连接的,其一直运行以便服务器一直运行,延迟或睡眠1秒来保证服务器不会吃掉所有的CPU。
  • 当服务器获得了客户机的一个请求时,服务器准备日期和时间写在客户机的socket上通过accept()函数返回其值。

三次握手是用来创建远程TCP连接的过程,我将会很快发一个关于TCP协议理论层面的帖子。

最终,我们完成了运行服务器的例子。


Socket Client Example

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h> 

int main(int argc, char *argv[])
{
    int sockfd = 0, n = 0;
    char recvBuff[1024];
    struct sockaddr_in serv_addr; 

    if(argc != 2)
    {
        printf("\n Usage: %s <ip of server> \n",argv[0]);
        return 1;
    } 

    memset(recvBuff, '0',sizeof(recvBuff));
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    } 

    memset(&serv_addr, '0', sizeof(serv_addr)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000); 

    if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
    {
        printf("\n inet_pton error occured\n");
        return 1;
    } 

    if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
       printf("\n Error : Connect Failed \n");
       return 1;
    } 

    while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
    {
        recvBuff[n] = 0;
        if(fputs(recvBuff, stdout) == EOF)
        {
            printf("\n Error : Fputs error\n");
        }
    } 

    if(n < 0)
    {
        printf("\n Read error \n");
    } 

    return 0;
}

在上述的程序中,我们创建了客户端来连接到服务器端并且从服务器端接收日期和时间。在上述的代码中:

  • 我们可以看到,通过socket()函数创建了一个socket。
  • 像远程主机的IP地址和端口号等信息都捆绑在结构体中,通过调用connect()函数来连接。
  • 注意,这里我们没有将客户机的socket连接在一个特定的端口上,像客户机通常使用内核指定的端口号,这样客户机的socket可以和任意端口结合。但万一服务器已经是一个众所周知的socket,所以服务器和一个指定的端口连接,像HTTP服务器运行在80端口等,那里在客户机上没有这样的限制。
  • 一旦socket连接上了,服务器通过客户机的socket描述符向客户机的socket发送日期和时间,这样客户机可以通过正常的函数调用来读取信息。

现在执行客户机如下:

$ ./newsc 127.0.0.1
Sun Dec  18 22:22:14 2011

可以看到,我们成功的从服务器端得到了日期和时间。我们需要把服务器端的IP地址当参数发送过去来运行这个例子。如果你在一台机器上运行了客户机端和服务器端来测试,使用上述例子中的loop back ip。



本人英语水平有限,翻译有所偏差,请查看原文。

原文地址:
C Socket Programming for Linux with a Server and Client Example Code

阅读更多
换一批

没有更多推荐了,返回首页