socket编程:简单UDP服务器/客户端编程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket编程:简单UDP服务器/客户端编程相关的知识,希望对你有一定的参考价值。

    对于之前的TCP协议而言,他是可靠的字节流传输,而在socket编程中,在不需要保证数据传输正确安全的情况下。或者由用户自己完成传输确认情况/服务端客户端自己实现数据传输。套接字编程也提供了UDP协议的方法。


基于UDP(不是面向连接)的socket编程,分为客户端和服务器端。

客户端的流程如下:

(1)创建套接字(socket)

(2)和服务器端进行通信(sendto)

(3)关闭套接字

因为在socket编程中,UDP是针对数据报的数据传输,所以socket专门定义了UDP所使用的函数接口。


sendto函数:指向一指定目的地发送数据,sendto()适用于发送未建立连接的UDP数据包 

技术分享


返回值为整型,如果成功,则返回发送的字节数,失败则返回-1,并SOCKET_ERROR。

sockfd: 套接字  也就是我们所建立的sock文件描述符

buf: 待发送数据的缓冲区 在客户端中定义的栈存储数据缓冲区

len: 缓冲区长度   确认的发送长度。

flags:调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式:阻塞 可以定义为不同的发送方式,有各种不同的发送方式。0为阻塞发送。

addr:(可选)指针,指向目的套接字的地址。调用存储我们所连接套接字的信息,

addr:lenaddr所指地址的长度。


服务器端的流程如下:

(1)创建套接字(socket)

(2)将套接字绑定到一个本地地址和端口上(bind)

(3)用返回的套接字和客户端进行通信(recvfrom)

(4)返回,等待另一个客户请求。

(5)关闭套接字。

recvfrom函数:本函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。

技术分享

sockfd:标识一个已连接套接口的描述字。同上

buf:接收数据缓冲区。同上

len:缓冲区长度。同上

flags:调用操作方式。同上

src_addr:(可选)指针,指向装有源地址的缓冲区。——输出型参数。同上

addrlen:(可选)指针,指向from缓冲区长度值。——输入输出型参数。同上

返回值为整型,如果成功,则返回接收到的字节数,失败则返回-1和SOCKET_ERROR。


总结:其实对于UDP协议的服务端和客户端而言,他主要是满足UDP协议的数据快速传输,服务端和客户端快速访问,没有TCP的3次握手过程,他只需要确认数据发送出去,不需要保证数据的传输是否成功,所以对于这样的UDP服务器端客户端而言。他是比较简单的,服务端不需要进入监听和连接的建立没有accept(),listen(),客户端不需要connnet();


实现机制比较简单,但是有时候为了保证数据传输的有效,我们需要实现一些其他的机制,这个以后再更新。

我们先看一下简单版本的实现代码:

//server端
#include<stdio.h>                                                                      
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
void Usage(const char* proc)
{
    printf("%s [ip][port]\n");
}
int main(int argc,char* argv[])
{
    if(argc!=3){
        Usage(argv[0]);
        return 1;
    }
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0){
        perror("socket");
        return 2;
    }
    int _port=atoi(argv[2]);
    char* _ip=argv[1];
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
        perror("bind");
        exit(1);
    }
    char buf[1024];
    struct sockaddr_in remote;
    socklen_t len=sizeof(remote);
    while(1)
    {
        ssize_t _s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&remote,&len);
        if(_s>0)
        {
            buf[_s]=‘\0‘;
            printf("client:[ip:%s][port:%d]  %s",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port),buf);
         }
         else if(_s==0){
            printf("client close");
            break;
        }
        else
        {
            break;
        }
 
    }
    close(sock);
    return 0;
}
//client端
#include<stdio.h>                                                                      
#include<stdlib.h>
#include<errno.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
void Usage(const char* proc)
{
    printf("%s[ip][port]",proc);
}
int main(int argc,char* argv[])
{
    if(argc!=3){
        Usage(argv[0]);
        return 1;
    }
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0){
        perror("socket");
        return 2;
    }
    int _port=atoi(argv[2]);
    char* _ip=argv[1];
    struct sockaddr_in client;
    client.sin_family=AF_INET;
    client.sin_port=htons(_port);
    client.sin_addr.s_addr=inet_addr(_ip);
    char buf[1024];
    while(1)
    {
        ssize_t _s=read(0,buf,sizeof(buf)-1);
        if(_s>0){
            buf[_s]=‘\0‘;
        }
        _s=sendto(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,sizeof(client));
    }
    return 0;
}


下面我们来讲一下关于端口的问题,这涉及到上一个TCP中所遗留下来的问题:

首先我们需要了解一下端口是什么:

    (Port)相当于一种数据的传输通道。用于接受某些数据,然后传输给相应的服务,而电脑将这些数据处理后,再将相应的恢复通过开启的端口传给对方。一般每一个端口的开放的偶对应了相应的服务,要关闭这些端口只需要将对应的服务关闭就可以了。

对于端口而言,我们可以将它理解为任何一个在当我们的应用程序:进程需要进行通信的时候,我们进行网络上的消息传输需要识别,但是在同一IP下,我们无法具体识别是哪个进程进行提取信息。所以就出现了端口。

    每一种东西出现是解决某一种或某一类问题而出现的。在通讯过程中IP是固定只有那么多的,所以为了解决通讯地址的重复出现了端口的概念。就相当于电话的分机号码一样。


TCP和UDP采用16b的端口号来识别应用程序。那么这些端口号是如何选择的呢?

TCP和UDP采用16b的端口号来识别应用程序。那么这些端口号是如何选择的呢?

服务器一般都是通过知名端口号来识别的。例如,对于TCP/IP实现来说,每个FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(普通文件传输协议)服务器的UDP端口号都是69。任何TCP/IP实现所提供的服务都用知名的1~1 023之间的端口号。这些知名端口号由Internet号分配机构(Internet Assigned Numbers Authority, IANA)来管理。到1992年为止,知名端口号介于1~255之间。256~1 023之间的端口号通常都是由UNIX系统占用,以提供一些特定的UNIX服务,也就是说,提供一些只有UNIX系统才有的,而其他操作系统可能不提供的服务。现在IANA管理1~1 023之间所有的端口号。

Internet扩展服务与UNIX特定服务之间的一个差别就是telnet和rlogin,它们二者都允许通过计算机网络登录到其他主机上。telnet是采用端口号为23的TCP/IP标准,且几乎可以在所有操作系统上进行实现。相反,rlogin最开始时只是为UNIX系统设计的(尽管许多非UNIX系统现在也提供该服务),因此在20世纪80年代初,它的端口号为513,客户端通常对它所使用的端口号并不关心,只须保证该端口号在本机上是唯一的即可。客户端口号又称做临时端口号(即存在时间很短暂),这是因为它通常只是在用户运行该客户程序时才存在,而服务器则只要主机开着,其服务就运行。

大多数TCP/IP实现给临时端口分配1 024~5 000之间的端口号。大于5 000的端口号是为其他服务器预留的(Internet上并不常用的服务)。大多数Linux系统的文件/etc/services都包含了人们熟知的端口号。

技术分享        这个就是我们在程序中经常使用的8080端口。

  需要注意的一点是:
        计算机之间依照互联网
传输层TCP/IP协议不同的协议通信,都有不同的对应端口。所以,利用短信(datagram)的UDP,所采用的端口号码不一定和采用TCP的端口号码一样。以下为两种通信协议的端口列表链接:


然后在上一篇博文中我们留下的一个问题就是server端主动关闭后,如何避免2MSL的TIME_WAIT.

其实就是采取了端口复用的规则,也就是socket的一个服务端的地址:端口能够重复使用,跟IP复用是同一个道理,都是为了解决重复相关的问题。

解决上述的问题使server端避免进入2MSL时间在结束之后就能尽快的再次进行全网的监听呢?可以使用setsockopt函数,设置socket描述符的选项SO_REUSEADDR为1,其作用是可以允许重用本地端口号和地址:

也就是修改套接字的描述符:

函数原型:

技术分享

这就是关于套接字选项的设置,以后我会专门写一篇博客来说明套接字选项。现在我们只需要知道如何避免2MSL

    函数参数中,

    sockfd是创建出的socket文件描述符;

    level被指定为SOL_SOCKET

    optname是对相应协议模块的描述,也就是设置sockfd描述符的选项,这里就设置为SO_REUSEADDR,允许重用本地地址和端口;

    optvaloptlen用于访问选项值,也就是optname,这里的值应设置为1

    函数成功返回0,失败返回-1并置相应的错误码;

    在创建监听socket和绑定函数bind之间插入函数setsockopt。

本文出自 “剩蛋君” 博客,请务必保留此出处http://memory73.blog.51cto.com/10530560/1782996

以上是关于socket编程:简单UDP服务器/客户端编程的主要内容,如果未能解决你的问题,请参考以下文章

linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)

Linux:UDP Socket编程(代码实战)

Linux:UDP Socket编程(代码实战)

Java udp socket编程

Linux socket编程示例(最简单的TCP和UDP两个例子)

网络编程TCP/UDP(socket编程)