精华内容
下载资源
问答
  • linux套接字编程

    千次阅读 2013-08-18 14:41:41
    套接字编程的各级要素: `主机接口:网络地址ip `协议:特定的协议(TCP & UDP) `端口:client或server的进程终点 套接字 简单的说,套接字就是两个应用程序之间通信管道的终点,这个管道可以唯一的标志一...
    套接字编程的各级要素:
    `主机接口:网络地址ip
    `协议:特定的协议(TCP & UDP)
    `端口:client或server的进程终点

    套接字
    简单的说,套接字就是两个应用程序之间通信管道的终点,这个管道可以唯一的标志一条链接,而应用程序则通过套接字来操作这个管道通信。

    通信过程
    要想使不同主机的进程通信,就必须使用套接字,套接字是用socket()函数创建,如果需要C/S模式,则需要把server的套接字与地址和端口绑定起来,使用bind(),当上述操作完成后,便可使用listen()来监听这个端口,如果有其他程序来connect,那么server将会调用accept()来接受这个申请并为其服务。client是调用connect()来建立与server之间的连接,这时会使用传说中的三次握手来建立一条数据链接。当连接被建立后,server与client便可以通信了,通信可以使用read()/write(),send()/recv(),sendto()/recvfrom()等一些函数来实现,但是不同的函数作用和使用位置是不同的。当数据传送完后,可以调用close()来关闭server与client之间的链接。上述过程就是不同主机之间进程通信的大致过程,当然这只是一个概要,其中的细节还是很多的。

    创建和清除套接字
    创建套接字的原型如下
    复制代码
    int socket(int domain, int type, int protocol);
    复制代码

    函数socket返回一个套接字对象,是一个整数类型。
    参数一:使用什么协议,
    AF_INET -- IPv4
    AF_INET6 -- IPv6。
    参数二:套接字的类型,
    SOCK_STREAM -- TCP
    SOCK_DGRAM -- UDP
    参数三:指明要使用的特殊协议,通常只有流类型和数据报类型的需要特殊协议,一般设置为0
    套接字地址:
    为了进行网络通信,使用结构体sockadr_in来命名套接字:

    复制代码
    //定义 struct sockaddr_in{ int16_t sin_family; uint16_t sin_port; struct in_addr sin_addr; char sin_zero[8]; }; struct in_addr{ unint32_t s_addr; }; //初始化 struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1"); serveraddr.sin_port = htons(SERVER_PORT);
    复制代码

    sin_family一般设置为AF_INET,sin_port为套接字的端口号,sin_addr指明主机的地址。
    inet_addr()可以将一个字符串类主机地址转化为32为的地址号,这里它不是简单的把四个8位的数字按照对应的位置拼接成一个32位地址,而是按照相反的方向拼接,也就是说192.168.128.65拼接成的32位数字为十进制65 128 168 192所对应的二进制32位结构体。
    htonl()与htons()的解释可以看帮助手册:
    DESCRIPTION
    The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
    The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
    The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
    The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
    On the i80x86 the host byte order is Least Significant Byte first, whereas the network byte order, as used on the Internet, is Most Significant Byte first.


    函数bind
    bind提供对套接字进行本地命名的方法,这个函数可以用于client与server套接字进行命名,一般更多用于服务器:

    复制代码
    int bind(int sock, struct sockaddr* addr, int addrLen); err = bind( serversock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
    复制代码

    函数listen
    server要想接受client的连接,必须使用listen()来不断的监听一个server的端口:
    复制代码
    int listen( int sock, int backlog);
    复制代码
    参数sock:表示前面已经创建的服务器套接字
    参数backlog:表示等待连接的客户端队列中一共可以有多少个客户端


    函数accept
    这个函数可以是server来授予客户端提出的连接,在调用accept之前,必须满足下面三个条件:
    `已经创建了server套接字 socket()
    `已经这个套接字绑定了一个名字 bind()
    `已经在监听server端口 listen()

    复制代码
    //desciption int accept( int sock, struct sockaddr* addr, socklen_t* addrlen); //prog int len = sizeof(struct sockaddr); ret = accept( serversock, (struct sockaddr*)&serveraddr, (socklen_t*)&len); if(ret >= 0) printf("succ"); else printf("fail");
    复制代码
    返回值:0表示成功,-1表示失败
    参数一:server的套接字
    参数二:server的套接字地址
    参数三:套接字地址的长度地址,注意要转化 (socklen_t*)

    函数connect
    这个函数被申请连接的一方使用,一般是client:
    复制代码
    //desciption int connect( int sock, struct sockaddr* addr, socklen_t addrlen); //prog int ret = connect( clientsock, (struct sockaddr*)&serveraddr, (socklen_t)sizeof(serveraddr)); if(ret >= 0) printf("succ"); else printf("fail")
    复制代码
    返回值:0表示成功,-1表示失败
    参数一:client套接字
    参数二:server的套接字地址
    参数三:套接字地址的长度,注意要转化 (socklen_t)


    连接套接字输入输出send/recv
    原型:

    复制代码
    int send( int sock, const void* msg, int len, unsigned int flags); int recv( int sock, void* buf, int len, unsigned int flags);
    复制代码
    返回值:如果成功返回字符串长度,否则返回-1
    参数:sock是套接字,msg/buf分别代表发送/接受的字符串,flags为标志
    flags:具体可从linux man pages中找到。

    非连接套接字函数sendto/recvfrom
    这两个函数多数用在UDP的无连接环境下,原型如下:
    复制代码
    int sendto( int sock, const void* msg, int len, unsigned int flags, const struct sockaddr* to, socklen_t tolen); int recvfrom( int sock, void* buf, int len, unsigned int flags, struct sockaddr* from, socklen_t fromlen);
    复制代码

    套接字选项
    为获取某个套接字的选项和这只选项的函数原型如下:
    复制代码
    int getsockopt( int sock, int level, int optname, void* optval, socklen_t* optlen); int setsockopt( int sock, int level, int optname, const void* optval, socklen_t optlen);
    复制代码

    示例

    下面两个程序分别为客户端和服务器端,由server监听端口,client申请连接,连接成功后,server分别利用send和write函数向client发送数据,当数据接受成功后,client向server返回成功标识

    server

    复制代码
    /* prog server */ #include <stdio.h> #include <unistd.h> #include <wait.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <ctime> #include <arpa/inet.h> using namespace std; #define MAXN_STR 40 /* server的端口号 */ int SERVER_PORT; /* 随即生成端口号 */ int readport() { srand(unsigned(time(NULL))); SERVER_PORT = 30000+rand()%10000; FILE* fout = fopen("port.in","w"); fprintf(fout,"%d\n",SERVER_PORT); fclose(fout); return 0; } int main() { int serverfd,connectionfd; int lenclient,lenserver; int cnt = 0; /* 定义服务器端和客户端的套接字 */ struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; char buff[MAXN_STR+1]; /* server每次随机生成端口号,并且写入port.in文件供client读取 */ readport(); /* 创建一个套接字 & 初始化 */ serverfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(SERVER_PORT); lenclient = sizeof(clientaddr); lenserver = sizeof(serveraddr); /* 对套接字进行本地命名 */ bind(serverfd, (sockaddr*)&serveraddr, sizeof(serveraddr)); printf("Socket has been created!\n"); printf("Server port : %d\n",SERVER_PORT); /* 对套接字进行监听 */ listen(serverfd,5); while(true) { printf("===== %d =====\n",cnt++); printf("Now listening...\n"); /* 接受客户端申请的连接 */ connectionfd = accept(serverfd,(struct sockaddr*)&clientaddr,(socklen_t*)&lenclient); /* 如果client成功连接到server, 则执行 */ if(connectionfd >= 0) { printf("Now the link has been connected.\n"); /* 从客户端的套接字中提取出IP地址 和其他信息*/ int clientip = clientaddr.sin_addr.s_addr; printf("Client ip : %d.%d.%d.%d\n",clientip&255,(clientip>>8)&255, (clientip>>16)&255,(clientip>>24)&255); printf("Client prot : %d\n",ntohs(clientaddr.sin_port)); /* 使用send向client发送信息 */ sprintf(buff,"THE SEND MSG"); printf("[SEND] Starting sending [send] msg ...\n"); send(connectionfd, (void*)buff, strlen(buff),0); recv(connectionfd, (void*)buff, MAXN_STR, 0 ); if(strlen(buff) > 0) printf("[SUCC] Sending succeed.\n"); else printf("[FAIL] Sending failed.\n"); /* 使用write向client发送消息 */ sprintf(buff,"THE WRITE MSG"); printf("[SEND] starting sending [write] msg ...\n"); write(connectionfd,buff, strlen(buff)); recv(connectionfd, (void*)buff, MAXN_STR, 0 ); if(strlen(buff) > 0) printf("[SUCC] Sending succeed.\n"); else printf("[FAIL] Sending failed.\n"); /* 关闭此连接 */ close(connectionfd); printf("Disconnect the link.\n"); /* 使用sendto向client发送消息(非连接) */ /* sprintf(buff, "THE SENDTO MSG"); printf("[SEND] Starting sending [sendto] msg ...\n"); */ /* sendto(serverfd, (void*)buff, strlen(buff), 0, (struct sockaddr*)&clientaddr, sizeof(clientaddr)); */ } else { /* 与client连接失败 */ printf("ERROR: Failed while establish the link!\n"); } } close(serverfd); return 0; }
    复制代码

    client

    复制代码
    /* prog client */ #include <sys/socket.h> #include <arpa/inet.h> #include <sys/stat.h> #include <sys/types.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <time.h> #include <stdlib.h> #include <wait.h> #define MAXN_STR 360 /* server端口号 */ int SERVER_PORT; int CLIENT_PORT; /* 读取server端口 并且 随即打开client端口 */ int readport() { srand(unsigned(time(NULL))); FILE* fout = fopen("port.in","r"); fscanf(fout,"%d",&SERVER_PORT); fclose(fout); CLIENT_PORT = 40000 + rand()%10000; return 0; } int main() { char buff[MAXN_STR]; char succ[] = "succ"; char fld[] = "fail"; /* client的套接字 */ int clientfd; /* server的套接字地址 */ struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; /* 读取server端口号 */ readport(); /* 创建client的套接字并且初始化server和client的套接字地址 */ clientfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SERVER_PORT); serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1"); clientaddr.sin_family = AF_INET; clientaddr.sin_port = htons(CLIENT_PORT); clientaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /* 绑定client套接字 */ bind(clientfd, (struct sockaddr*)&clientaddr, sizeof(clientaddr)); printf("Socket has been created.\n"); printf("Client port : %d\n",CLIENT_PORT); /* 向server请求服务 */ printf("Now require scv from server ...\n"); int chk = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); /* 判断是否连接请求成功 */ if(chk < 0) { printf("ERROR: Could not connect to the host!\n"); return 0; } else { /* 连接成功,并且输出server的信息 */ printf("connection established.\n"); int serverip = serveraddr.sin_addr.s_addr; printf("Server ip : %d.%d.%d.%d\n",serverip&255,(serverip>>8)&255, (serverip>>16)&255,(serverip>>24)&255); printf("Server port : %d\n",ntohs(serveraddr.sin_port)); } /* 使用recv从server接受数据 */ printf("Starting RECV msg ...\n"); int len = recv(clientfd,(void*)buff,MAXN_STR,0); buff[len] = 0; if(len > 0) { /* 如果client接受数据成功,则向server发送成功信号 */ printf("[RECV] %s\n",buff); printf("[SUCC] Recviving succeed.\n"); send(clientfd,(void*)succ,strlen(succ),0); } else { /* 否则,向server发送失败信号 */ printf("[FAIL] Recviving failed.\n"); send(clientfd,(void*)fld,strlen(fld),0); } /* 使用read从server读取数据 */ printf("Starting READ msg ...\n"); len = read(clientfd,buff,MAXN_STR); buff[len] = 0; if(len > 0) { /* 如果client接受数据成功,则向server发送成功信号 */ printf("[RECV] %s\n",buff); printf("[SUCC] Recviving succeed.\n"); send(clientfd,(void*)succ,strlen(succ),0); } else { /* 否则,向server发送失败信号 */ printf("[FAIL] Recviving failed.\n"); send(clientfd,(void*)fld,strlen(fld),0); } /* 断开与server的连接 */ close(clientfd); printf("Now the connection has been broken\n"); /* 使用recvfrom从server接受数据(非链接) */ /* int serverlen = sizeof(serveraddr); printf("Starting RECVFROM msg ...\n"); len = recvfrom(clientfd, (void*)buff, MAXN_STR, 0, (struct sockaddr*)&serveraddr, (socklen_t*)&serverlen); buff[len] = 0; if(len > 0) { // 如果client接受数据成功,则向server发送成功信号 printf("[RECV] %s\n",buff); printf("[SUCC] Recviving succeed.\n"); } else { // 否则,向server发送失败信号 printf("[FAIL] Recviving failed.\n"); } */ close(clientfd); return 0; }
    复制代码

    展开全文
  • 1、套接字、ip、端口介绍 1)、套接字 源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。 它是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息...

    1、套接字、ip、端口介绍

    1)、套接字

    源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
    它是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
    或者说,套接字,是支持TCP/IP网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
    非常非常简单的举例说明下:Socket=Ip address+ TCP/UDP + port。
    区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。Socket原意是 “插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
    Socket可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定与网络驱动建立关系。此后,应用程序送给Socket的数据,由Socket交给网络驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定IP地址和端口号相关的数据后,由网络驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据,网络应用程序就是这样通过Socket进行数据的发送与接收的。
     

    2 )、ip

    IP地址是一个32位的无符号整数,由于没有转变成二进制,因此通常以小数点分隔,如:198.163.227.6,正如所见IP地址均由四个部分组成,每个部分的范围都是0-255,以表示8位地址。IP地址都是32位地址,这是IP协议版本4(简称Ipv4)规定的,目前由于IPv4地址已近耗尽,所以IPv6地址正逐渐代替Ipv4地址,Ipv6地址则是128位无符号整数。
     

    3 )、端口

    端口号是一个16位无符号整数,范围是0-65535

     

    2、套接字的分类

     

    常用的TCP/IP协议的3种套接字类型如下所示。
    流套接字(SOCK_STREAM):
    流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。
    数据报套接字(SOCK_DGRAM):
    数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
    原始套接字(SOCK_RAW):
    原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW
    原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据包套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

     

    3、Socket网络层次

    套接字位于网络中的层次,它位于传输层以上、应用层以下。Socket编程正是通过一系列系统调用(Socket API)来完成应用层协议(如ftp、http)。

    套接字是对网络中应用层进程之间的通信进行了抽象,提供了应用层进程利用网络协议栈交换数据的机制。

    或者

     

     

     

    展开全文
  • Linux 套接字编程基础

    千次阅读 2016-10-15 15:37:55
    套接字中包含了端口号,用来确定进程,一个端口号一次只能分配给一个进程,即端口号与进程是一一对应的;一、套接字的结构1、IPv4套接字地址结构 IPv4地址结构命名为sockaddr_in,定义在头文件中,结构定义如下:...

    套接口即网络进程的ID;网络通信归根到底即为进程间的通信;套接字中包含了端口号,用来确定进程,一个端口号一次只能分配给一个进程,即端口号与进程是一一对应的;

    一、套接字的结构

    1、IPv4套接字地址结构

      IPv4地址结构命名为sockaddr_in,定义在<netinet/in.h>头文件中,结构定义如下:

    struct sockaddr_in {
        sa_family_t sin_family;    //IPV4协议为AF_INET,协议族
        in_port_t sin_port;        //16位端口号,网络字节序列
        struct in_addr sin_addr;   //32位IP地址
        unsigned char sin_zero[8]; //备用域;
    };
    struct in_addr{
        in_addr_t s_addr;          //32位IP地址,网络字节序列
    } ;

    2、通用套接字地址结构

    struct sockaddr{
        sa_family_t sa_family;
        char sa_data[14]
    }

    通用套接字结构本质上和上面的IPv4套接字是一致的,通用套接字只是在函数调用的过程中用于参数的传递,定义时用sockaddr_in定义,在函数调用时通过类型转换来使用。可以参考sockaddr和sockaddr_in的区别 。套接字结构除了这两种常见的外还有基于IPv6和Unix域的结构sockaddr_in6,sockaddr_un等。

    二、相关处理函数

    1、字节排序函数

    #include <netinet/in.h>
    uint16_t htons(uint16_t host16bitvalue);    //返回网络字节序的值
    uint32_t htonl(uint32_t host32bitvalue);    //返回网络字节序的值
    uint16_t ntohs(uint16_t net16bitvalue);     //返回主机字节序的值
    uint32_t ntohl(uint32_t net32bitvalue);     //返回主机字节序的值

    其中,h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换,16bit和32bit分别用于端口号和IP地址。有时可以用任何地址则用INADDR_ANY,INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。

    2、字节操作函数

      字节操作函数主要是用于操作结构体中的某几个字节,在处理套接字结构中有IP地址等字段,包含0字节却不是C字符串,需要特定的函数来进行处理。

    #include <string.h>
    void bzero(void *dest,size_t nbytes);
    void bcopy(const void *src,void *dest,size_t nbytes);
    int bcmp(const void *prt1,const void *ptr2,size_t nbytes);  //相等返回0,否则非0
    
    void *memset(void *dest,int c,size_t len);
    void *memcpy(void *dest,const void *src,size_t nbytes);
    int memcmp(const void *ptr1,const void *ptr2,size_t nbytes); //相等返回0,否则非0

      以b打头的函数为支持套接口函数的系统所提供,mem为支持ANSI C库提供的函数;其中,bzero将指定数目的字节设置为0,bcopy将指定数目的字节从源字节串复制到目的字节串。bcmp比较两个字节串,相同返回0。memset将目标中指定数据的字节设置为指定的值(不一定是0),memcpy为复制,memcmp为比较,两者与前面类似。

    3、地址转换函数

      一般使用ASCII字符串表示IP地址,也就是点分十进制数表示(218.170.19.1),但在套接字中使用32位网络字节序,保存的是二进制值,因此必须进行地址转换。

    #include <arpa/inet.h>
    in_addr_t inet_addr(const char *straddr);    //字符串有效返回网络字节序的IP地址,否则INADDR_NONE
    int inet_aton(const char* straddr,struct in_addr *addrp);//字符串有效返回1,否则0
    char* inet_ntoa(struct in_addr inaddr); //返回一个字符串指针
    
    int inet_pton(int family, const char* str, void* addr);
    //成功返回1,字符串无效返回0,出错返回-1
    const char* inet_ntop(int family, const void* addr, char* str, size_t len);
    //成功返回结果指针,出错返回NULL
    
    /*说明
     *后面两个函数为新函数,支持IPv4和IPv6,family用来指定:AF_INET,AF_INET6
     *inet_ntop函数len为目标存储单元大小,str指针就是函数返回值
     */

       inet_aton将一个字符串转换为32位网络字节序二进制值,用结构in_addr存储。inet_addr功能相同(废弃,不使用),inet_ntoa进行相反的操作。
       
    4、字节流读取函数

      在套接字通信中进行字节读取函数:read(),write()。与I/O中的读取函数略有区别,因为它们输入或输出的字节数比可能比请求的少。

    ssize_t write(int fd, const void*buf,size_t nbytes);
    ssize_t read(int fd,void *buf,size_t nbyte); 
    
    /*说明
     *函数均返回读或写的字节个数,出错则返回-1
     */

      第一个将buf中的nbytes个字节写入到文件描述符fd中,成功时返回写的字节数。第二个为从fd中读取nbyte个字节到buf中,返回实际所读的字节数。详细应用说明参考使用read write 读写socket(套节字)
      网络I/O还有一些函数,例如:recv()/send(),readv()/writev(),recvmsg()/sendmsg(),recvfrom()/sendto()等,后面进行详细介绍。

    三、socket通信相关函数

    1、socket 函数

      为了执行网络I/O,进程必须做的第一件事就是执行socket函数,指定期望的通信协议类型。套接字是通信端点的抽象,实现端对端之间的通信,访问套接字需要套接字描述符。套接字描述符通过socket 函数获得,这样才能对套接字进行操作。

        /*  
         * 函数功能:创建套接字描述符;  
         * 返回值:若成功则返回套接字非负描述符,若出错返回-1;  
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int socket(int family, int type, int protocol);   
        /*  
         * 说明:  
         * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;  
         * family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:  
         * (1)AF_INET         IPv4因特网域  
         * (2)AF_INET6        IPv6因特网域  
         * (3)AF_UNIX         Unix域  
         * (4)AF_ROUTE        路由套接字  
         * (5)AF_KEY          密钥套接字  
         * (6)AF_UNSPEC       未指定  
         *  
         * type确定socket的类型,常用类型如下:  
         * (1)SOCK_STREAM     有序、可靠、双向的面向连接字节流套接字  
         * (2)SOCK_DGRAM      长度固定的、无连接的不可靠数据报套接字  
         * (3)SOCK_RAW        原始套接字  
         * (4)SOCK_SEQPACKET  长度固定、有序、可靠的面向连接的有序分组套接字  
         *  
         * protocol指定协议,常用取值如下:  
         * (1)0               选择type类型对应的默认协议  
         * (2)IPPROTO_TCP     TCP传输协议  
         * (3)IPPROTO_UDP     UDP传输协议  
         * (4)IPPROTO_SCTP    SCTP传输协议  
         * (5)IPPROTO_TIPC    TIPC传输协议  
         *  
         */   

    2、connect 函数

      TCP用户用connect函数与服务器建立连接。

        /*  
         * 函数功能:建立连接,即客户端使用该函数来建立与服务器的连接;  
         * 返回值:若成功则返回0,出错则返回-1;  
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);   
        /*  
         * 说明:  
         * sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符;  
         * servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址;  
         * addrlen是目的套接字地址的大小;  
         *  
         * 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号,即客户端connect前不一定要调用bind()函数;  
         */   

    3、bind 函数

      bind()函数用于将一个网络地址赋予一个套接字,因为套接字在创建之初是没有地址的需要进行赋值,这里的地址一般为网络IP和端口号。

        /*  
         * 函数功能:将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号;  
         * 返回值:若成功则返回0,若出错则返回-1;  
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);   
        /*  
         * 说明:  
         * sockfd 为套接字描述符;  
         * addr是一个指向特定协议地址结构的指针,通用套接字结构;  
         * addrlen是地址结构的长度;  
        */   

      对于 TCP 协议,调用 bind 函数可以指定一个端口号,或指定一个 IP 地址,也可以两者都指定,还可以都不指定。若 TCP 客户端或服务器端不调用bind 函数绑定一个端口号,当调用connect 或 listen 函数时,内核会为相应的套接字选择一个临时端口号。一般 TCP 客户端使用内核为其选择一个临时的端口号,而服务器端通过调用bind 函数将端口号与相应的套接字绑定。进程可以把一个特定的 IP 地址捆绑到它的套接字上,但是这个 IP 地址必须属于其所在主机的网络接口之一。对于 TCP 客户端,这就为在套接字上发送的 IP 数据报指派了源 IP 地址。对于 TCP 服务器端,这就限定该套接字只接收那些目的地为这个 IP 地址的客户端连接。TCP 客户端一般不把 IP 地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源 IP 地址,而所用外出接口则取决于到达服务器端所需的路径。若 TCP 服务器端没有把 IP 地址捆绑到它的套接字上,内核就把客户端发送的 SYN 的目的 IP 地址作为服务器端的源 IP 地址。

    4、listen 函数

      listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。

         /*  
          * 函数功能:接收连接请求;  
          * 若成功则返回0,若出错则返回-1;   
          * 函数原型:  
          */   
         #include <sys/socket.h>   
         int listen(int sockfd, int backlog);
         /*  
          * sockfd是套接字描述符;  
          * backlog是该进程所要入队请求的最大请求数量;  
          */  

      内核为任何一个给定监听套接字维护两个队列:

    • 未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接字处于 SYN_REVD 状态;
    • 已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态;

    5、accept 函数

      accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

        /* 函数功能:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠;  
         * 返回值:若成功返回套接字描述符,出错返回-1;   
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
        /*  
         * 说明:  
         * 参数 cliaddr 和 addrlen 用来返回已连接的对端(客户端)的协议地址;  
         *  
         * 该函数返回套接字描述符,该描述符连接到调用connect函数的客户端;  
         * 这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接,  
         * 而是继续保持可用状态并接受其他连接请求;  
         * 若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址,  
         * 并且将addrlen设为指向代表这个缓冲区大小的整数指针;  
         * accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小;  
         * 若没有连接请求等待处理,accept会阻塞直到一个请求到来;  

      该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。

    6、fork 函数

        /* 函数功能:创建子进程;  
         * 返回值:  
         * (1)在子进程中,返回0;  
         * (2)在父进程中,返回新创建子进程的进程ID;  
         * (3)若出错,则范回-1;  
         * 函数原型:  
         */   
        #include <unistd.h>   
        pid_t fork(void);   
        /* 说明:  
         * 该函数调用一次若成功则返回两个值:  
         * 在调用进程(即父进程)中,返回新创建进程(即子进程)的进程ID;  
         * 在子进程返回值是0;  
         * 因此,可以根据返回值判断进程是子进程还是父进程;  
         */   

      任何一个子进程只有一个父进程,一个父进程可以有多个子进程,fork函数在调用父进程中返回一次,返回新派生的进程ID号,在子进程又返回一次,返回值为0,可以根据返回值判断进程是父进程还是子进程。下面为与进程处理相关的几个简单函数介绍。
      

        /* getppid
         * 函数功能:获取当前进程父进程id;  
         * 返回值:父进程id
         * 函数原型:  
         */   
        #include <unistd.h>   
        pid_t getppid(void);   
        /* 说明:  
         * 在调用中不能返回错误;
         * getpid(void)为获取当前调用进程的id;
         */   
    
        /* kill
         * 函数功能:给进程发送信号
         * 返回值:成功返回0,失败返回-1;
         * 函数原型:
         */
        #include <signal.h>
        int kill(pid_t pid, int sig)
        /* 说明:
         * pid为被发送信号的进程id,sig为信号编号,pid的取值有几种情况:
         * 1、pid > 0 :将此信号发送给进程ID为pid的进程;
         * 2、pid = 0 :将此信号发送给当前进程组内所有的进程;
         * 3、pid < 0 :将此信号发送给进程组识别码为pid绝对值的所有进程;
         * 4、pid = -1:将信号广播传送给系统内所有的进程;
         */
    
        /* pause
         * 函数功能:使当前进程睡眠,直到接收到信号;
         * 返回值:-1,EINTR(接收到中断信号);
         * 函数原型:
         */
        #include <unistd.h>
        int pause(void);

    7、exec 函数

      存放在硬盘上的可执行文件能够被Unix系统执行的唯一方法是由一个现有进程调用6个exec函数中的一个。exec把当前进程映像替换成新的程序文件,而该新程序通常从main函数开始执行,进程ID并不改变,一般称调用exec的进程为调用进程。

        /* exec 函数 */   
        /*  
         * 函数功能:把当前进程替换为一个新的程序文件,进程ID并不改变;  
         * 返回值:若出错则返回-1,若成功则不返回;  
         * 函数原型:  
         */   
        #include <unistd.h>   
        int execl(const char *pathname, const char *arg, ...);   
        int execv(const char *pathnam, char *const argv[]);   
        int execle(const char *pathname, const char *arg, ... , char *const envp[]);   
        int execve(const char *pathnam, char *const argv[], char *const envp[]);   
        int execlp(const char *filename, const char *arg, ...);   
        int execvp(const char *filename, char *const argv[]);   
        /*  6 个函数的区别如下:  
         * (1)待执行的程序文件是由文件名还是由路径名指定;  
         * (2)新程序的参数是一一列出还是由一个指针数组来引用;  
         * (3)把调用进程的环境传递给新程序还是给新程序指定新的环境;  
         *  只有execve是内核中的系统调用,其它5个函数都是调用execve的库函数,关系如下图
         */   

             这里写图片描述

    8、close 函数

        /* 
         * 函数功能:关闭套接字,若是在 TCP 协议中,并终止 TCP 连接;  
         * 返回值:若成功则返回0,若出错则返回-1;  
         * 函数原型:  
         */   
        #include <unistd.h>   
        int close(int sockfd);   
        /*
         * close之后套接字描述符不能再由调用进程使用,即不能作为read和write参数;
         * 在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共,
         * 享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相,
         * 应的减一,当引用计数仍大于零时,这个close调用就不会引发TCP的四路握手断,
         * 连过程。
         * shutdown函数也有类似的功能;
         */

    9、shutdown 函数

        /* 
         * 函数功能:关闭套接字;  
         * 返回值:若成功则返回0,若出错则返回-1;  
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int shutdown(int sockfd, int howto);   
        /*
         * sockfd为需要关闭的套接字描述符,howto用来指定函数的行为;
         * howto的3种取值方式:
         * 1.SHUT_RD:值为0,关闭连接的读这一半;
         * 2.SHUT_WR:值为1,关闭连接的写这一半;
         * 3.SHUT_RDWR:值为2,连接的读和写都关闭。
         */

      close与shutdown区别:
      close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说。
      而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。
      linux网络编程之shutdown() 与 close()函数详解

    10、getsockname ,getpeername 函数

        /*  
         * 函数功能:获取已绑定到一个套接字的地址;  
         * 返回值:若成功则返回0,若出错则返回-1;  
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int getsockname(int sockfd, struct sockaddr *addr, socklen_t *alenp);   
        /*  
         * 说明:  
         * 调用该函数之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小;  
         * 返回时,该整数会被设置成返回地址的大小,如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错;  
         */   
    
        /*  
         * 函数功能:获取套接字对方连接的地址;  
         * 返回值:若成功则返回0,若出错则返回-1;  
         * 函数原型:  
         */   
        #include <sys/socket.h>   
        int getpeername(int sockfd, struct sockaddr *addr, socklen_t *alenp);   
        /*  
         * 说明:  
         * 该函数除了返回对方的地址之外,其他功能和getsockname一样;  
         */   

      当不调用bind或调用bind没有指定本地IP地址和端口时,可以调用函数getsockname来返回内核分配的本地IP地址和端口号,还可以获取到套接口的协议簇。当一个新的连接建立时,服务器端也可以调用getsockname来获取分配给此连接的本地IP地址;当服务器端的一个子进程调用函数exec启动执行时,只能调用getpeername来获取客户端IP地址和端口号。

    四、并发服务器

      大多数TCP服务器是并发的,它们为每一个待处理的客户连接调用fork派生一个子进程,当一个连接建立时,accept返回,服务器调用fork函数新建一个子进程,由子进程处理与客户的连接,父进程则可以在监听套接字上再次调用accept来处理下一个客户的连接。
      
    所有的客户端和服务器都是从调用socket开始的,它返回一个描述符,客户端调用connect,服务器调用bind,listen,accept。建立连接之后I/O函数进行数据的传递。通信结束一般用close关闭套接字。

    部分参考资料:
    网络编程学习笔记一:Socket编程
    基本 TCP 套接字编程讲解
    套接字编程基础
    UNIX网络编程卷1:套接字联网API;

    五、简单客户/服务程序
      
      下面为在UNIX网络编程卷1:套接字联网API中摘录的一个简单TCP网络通信程序,有适当修改。

    /*
     * 服务器端;
     * 实现从客户端获取数据并重新写回服务器;
     * 每一个客户端访问都通过子进程处理;
     * 提供并发服务;
     */
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <errno.h>
    
    void str_echo(int sockfd);    //回射函数声明
    int main(int argc, char** argv)
    {
        int listenfd, connfd;        
        pid_t childpid;
        socklen_t clilen;
        struct sockaddr_in cliaddr, servaddr;
        listenfd = socket(AF_INET, SOCK_STREAM, 0); //获取监听套接字描述符
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(6666); //服务器套接字赋值
        bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)); //绑定套接字
        listen(listenfd, 5); //监听,5用来规定最大连接数
    
        for(;;)
        {
            clilen = sizeof(cliaddr);
            connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen); //接受连接
            if((childpid = fork()) == 0 ) 
            {//创建子进程,由子进程处理连接
                close(listenfd);
                str_echo(connfd);
                exit(0);
            }
            close(connfd);
        }
    }
    
    void str_echo(int sockfd)
    {
        ssize_t n;
        char buf[256] = "";  //空字符串
        while( (n = read(sockfd, buf, 256)) > 0)
        {
            write(sockfd, buf, n);
            printf("receive message: %s\n",buf);
            if(n < 0 )
                printf("read error!");
            bzero(buf, 256);    //字符串置0
        }
    }
    /*
     * 说明:
     * 这里buf在定义时赋值为一个空字符串,是为了在下面的printf输出中不产生乱码,
     * 因为read只读取获取的字节个数,当不足buf的256个时,buf后面为空,printf
     * 输出buf会把buf后面非接收字节输出(乱码);
     * 没有处理子进程中止信号,子进程进入僵死状态;
     */ 
    /*
     * 客户端;
     * 首先建立和服务器的连接,然后开始信息的传输;
     * 从标准输入端获取传输消息,发送到服务器;
     * 从服务器接收信息,输入到屏幕;
     */ 
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <netinet/in.h>
    
    int main(int argc, char** argv)
    {
        int sockfd;
        struct sockaddr_in servaddr;      //服务器套接字
        char* ips = "192.168.110.128";    //服务器IP
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);   //获取客户端套接字描述符
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(6666);  //端口号
        if(inet_aton(ips, &servaddr.sin_addr) <= 0)
        {
            printf("inet_aton error!");
            exit(0);
        }
        if(connect(sockfd, (struct sockaddr*)& servaddr, sizeof(servaddr)) < 0)
        {    //连接到服务器
            printf("connect error!");
            exit(0);
        }
        char sendline[256], recvline[256] = "";
        printf("Input message to be send:\n");
        while(fgets(sendline, 256, stdin) != NULL)
        {    //从标准输入获取发送信息
            if(write(sockfd, sendline, strlen(sendline)) < 0)
            {//发送消息到服务器
                printf("write message error!");
                exit(0);
            }
            if(read(sockfd, recvline, 256) < 0)
            {//接收服务器返回的消息
                printf("read message error");
              exit(0);
            }
            printf("%s",recvline);    //输出接收信息
            bzero(&recvline, 256);
            printf("Input message to be send:\n");
        }
        close(sockfd);  
        exit(0);
    }
    /*
     * 说明:
     * 客户端套接字没有bind其地址,由内核自动添加;
     * 当服务器关闭,客户端不会第一时间获知,再次发送信息才会读取到服务器发送的FIN;
     * 可以不断进行信息传输;
     */
    /*
     * 测试输出
     * 观察客户端和服务器的输出情况
     */
    
    /* 客户端 */
    [centos@localhost Documents]$ ./a.out     //启动客户端连接
    Input message to be send:
    hello,serve!                              //输入
    hello,serve!                              //接收输出
    Input message to be send:
    test for client
    test for client
    Input message to be send:
    ...
    
    /* 服务器端 */
    receive message: hello,serve!
    receive message: test for client
    ...
    
    /*
     * 说明:
     * 先启动服务器,在启动客户端进行连接并发送数据;
     * 客户端可以通过下面方式中止;
     * ctrl-c :发送 SIGINT 信号,终止一个进程;
     * ctrl-d :不发送信号, 表示输入EOF字符,程序中fgets会触发中止命令;
     * ctrl-z :发送SIGSTOP信号,挂起一个进程,可以用fg使之回到前台,不会终止客户端;
     */
    展开全文
  • 在《绑定( bind )端口需要注意的问题》提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 )。 实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候...

    在《绑定( bind )端口需要注意的问题》提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 )。


    实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(int argc, char *argv[])
    {
    	int sockfd_one;
    	int err_log;
    	sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
    	if(sockfd_one < 0)
    	{
    	perror("sockfd_one");
    	exit(-1);
    	}
    
    	// 设置本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port = htons(8000);		// 端口为8000
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    	// 绑定,端口为8000
    	err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind sockfd_one");
    		close(sockfd_one);		
    		exit(-1);
    	}
    
    	int sockfd_two;
    	sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two
    	if(sockfd_two < 0)
    	{
    		perror("sockfd_two");
    		exit(-1);
    	}
    
    	// 新套接字sockfd_two,继续绑定8000端口,绑定失败
    	// 因为8000端口已被占用,默认情况下,端口没有释放,无法绑定
    	err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind sockfd_two");
    		close(sockfd_two);		
    		exit(-1);
    	}
    
    	close(sockfd_one);
    	close(sockfd_two);
    
    	return 0;
    }
    

    程序编译运行后结果如下:



    那如何让sockfd_one, sockfd_two两个套接字都能成功绑定8000端口呢?这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。

    设置socket的SO_REUSEADDR选项,即可实现端口复用:

    int opt = 1;
    // sockfd为需要端口复用的套接字
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));

    SO_REUSEADDR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)

    1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。

    2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。

    3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。

    4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。


    需要注意的是,设置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用

    // sockfd_one, sockfd_two都要设置端口复用
    // 在sockfd_one绑定bind之前,设置其端口复用
    int opt = 1;
    setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );
    err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    
    // 在sockfd_two绑定bind之前,设置其端口复用
    opt = 1;
    setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,(const void *)&opt, sizeof(opt) );
    err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));

    端口复用完整代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(int argc, char *argv[])
    {
    	int sockfd_one;
    	int err_log;
    	sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
    	if(sockfd_one < 0)
    	{
    	perror("sockfd_one");
    	exit(-1);
    	}
    
    	// 设置本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port = htons(8000);		// 端口为8000
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	// 在sockfd_one绑定bind之前,设置其端口复用
    	int opt = 1;
    	setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, 
    					(const void *)&opt, sizeof(opt) );
    
    	// 绑定,端口为8000
    	err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind sockfd_one");
    		close(sockfd_one);		
    		exit(-1);
    	}
    
    	int sockfd_two;
    	sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two
    	if(sockfd_two < 0)
    	{
    		perror("sockfd_two");
    		exit(-1);
    	}
    
    	// 在sockfd_two绑定bind之前,设置其端口复用
    	opt = 1;
    	setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, 
    					(const void *)&opt, sizeof(opt) );
    	
    	// 新套接字sockfd_two,继续绑定8000端口,成功
    	err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind sockfd_two");
    		close(sockfd_two);		
    		exit(-1);
    	}
    
    	close(sockfd_one);
    	close(sockfd_two);
    
    	return 0;
    }
    

    端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。同时,这 n 个套接字发送信息都正常,没有问题。但是,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。


    下面,我们在之前的代码上,添加两个线程,分别负责接收sockfd_one,sockfd_two的信息:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <pthread.h>
    
    // 线程1的回调函数
    void *recv_one(void *arg)
    {
    	printf("===========recv_one==============\n");
    	int sockfd = (int )arg;
    	while(1){
    		int recv_len;
    		char recv_buf[512] = "";
    		struct sockaddr_in client_addr;
    		char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
    		socklen_t cliaddr_len = sizeof(client_addr);
    		
    		recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
    		printf("sockfd_one =========== data(%d):%s\n",recv_len,recv_buf);
    	
    	}
    
    	return NULL;
    }
    
    // 线程2的回调函数
    void *recv_two(void *arg)
    {
    	printf("+++++++++recv_two++++++++++++++\n");
    	int sockfd = (int )arg;
    	while(1){
    		int recv_len;
    		char recv_buf[512] = "";
    		struct sockaddr_in client_addr;
    		char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
    		socklen_t cliaddr_len = sizeof(client_addr);
    		
    		recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
    		printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s\n",recv_len,recv_buf);
    	
    	}
    
    	return NULL;
    }
    
    int main(int argc, char *argv[])
    {
    	int err_log;
    	
    	/sockfd_one
    	int sockfd_one;
    	sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
    	if(sockfd_one < 0)
    	{
    	perror("sockfd_one");
    	exit(-1);
    	}
    
    	// 设置本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port = htons(8000);		// 端口为8000
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	// 在sockfd_one绑定bind之前,设置其端口复用
    	int opt = 1;
    	setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, 
    					(const void *)&opt, sizeof(opt) );
    
    	// 绑定,端口为8000
    	err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind sockfd_one");
    		close(sockfd_one);		
    		exit(-1);
    	}
    	
    	//接收信息线程1
    	pthread_t tid_one;
    	pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);
    	
    	/sockfd_two
    	int sockfd_two;
    	sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two
    	if(sockfd_two < 0)
    	{
    		perror("sockfd_two");
    		exit(-1);
    	}
    
    	// 在sockfd_two绑定bind之前,设置其端口复用
    	opt = 1;
    	setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, 
    					(const void *)&opt, sizeof(opt) );
    	
    	// 新套接字sockfd_two,继续绑定8000端口,成功
    	err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind sockfd_two");
    		close(sockfd_two);		
    		exit(-1);
    	}
    	//接收信息线程2
    	pthread_t tid_two;
    	pthread_create(&tid_two, NULL, recv_two, (void *)sockfd_two);
    	
    	
    	while(1){	// 让程序阻塞在这,不结束
    		NULL;
    	}
    
    	close(sockfd_one);
    	close(sockfd_two);
    
    	return 0;
    }

    接着,通过网络调试助手给这个服务器发送数据,结果显示,只有最后一个套接字sockfd_two会正常接收数据:



    我们上面的用法,实际上没有太大的意义。端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中——那只好等等再重试了,麻烦!


    源代码下载请点此处。

    展开全文
  • linux套接字

    2013-11-16 00:17:21
    套接字连接的过程如同(客户)打一个电话到一个大公司,接线员(服务器进程)...本地套接字的名字是Linux文件系统中的文件名,一般放在/tmp或/usr/tmp目录中;网络套接字的名字是与客户连接的特定网络有关的服务标识符
  • linux 套接字接口

    千次阅读 2012-10-07 14:34:51
    IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在头文件里面,下面是POSIX定义 struct in_addr{  in_addr_t s_addr; //32bit IPv4 address, network byte ordered }; 这个...
  • Linux套接字

    万次阅读 2012-05-04 09:40:42
    套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。 套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。套接字还用...
  • //testSocket:已经绑定了端口套接字,这里假设不知道其绑定了哪个端口,本函数是通过该套接字反推其绑定的端口 //函数返回该套接字端口号 int getPort(SOCKET testSocket) { sockaddr_in sockAddr; int ...
  • 之前项目中涉及udp套接字编程,其中一个要求是获取客户端发过来报文的端口和ip地址,功能很简单,只是对这一块不很熟。之前使用的方法是通过调用recvmsg这个接口,并通过参数msg里面的msg_name来获取客户端地址,如下...
  • Linux套接字详解(二)----套接字Socket

    万次阅读 多人点赞 2015-06-02 19:59:23
    为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。套接口可以说是网络编程中一个非常重要的概念,linux以文件的形式实现套接口,与套接口相应的...
  • socket —— Linux套接字接口

    千次阅读 2013-06-25 13:36:10
    socket —— Linux套接字接口 SYNOPSIS #include sockfd = socket(int socket_family, int socket_type, int protocol); DESCRIPTION 此手册描述Linux网络套接层用户接口。BSD兼容的通用接口位于...
  • linux原始套接字实战

    千次阅读 2017-12-22 16:04:05
    本文的主线是一个使用原始套接字发送数据的程序,工作在数据链路层,采用自定义以太网帧协议,靠MAC地址识别目标主机。所以不涉及到IP地址和端口号。程序主要用于互联的两台机器之间进行丢帧率计算。以下部分都是...
  • Linux套接字编程5大陷阱

    千次阅读 2013-01-10 23:04:49
    Linux套接字编程5大陷阱 - 在异构环境开发可靠的网络应用 M. Tim Jones 摘要(summary这里翻译为摘要): 套接字API是网络应用开发事实上的标准API。尽管这些API很简单,但缺乏经验的开发人员...
  • linux下,在一个程序里,两个UDP套接字可以同时绑定同一主机的不同端口吗?该怎么实现呢?
  • Linux网络编程】套接字简介

    千次阅读 2019-11-11 21:02:59
    Socket套接字由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序...
  • Linux套接字实现服务器和客户端通信

    千次阅读 2016-05-28 13:57:57
    套接字是一种进程间通信的方法,不同于以往介绍的进程间通信方法的是,它并不局限于同一台计算机的资源,例如文件系统空间、共享内存或者消息队列。 套接字(socket)是一种通信机制,客户/服务器系统既可以在本地...
  • Linux套接字通信常用函数-bind

    千次阅读 2015-12-24 16:22:47
    bind函数主要用于套接字通信的服务器端,用于绑定服务器要监听的地址和端口,所需要的头文件 #include #include 函数原型 int bind(int socket,const struct sockaddr* address,socklen_t address_len...
  • Linux原始套接字学习总结

    千次阅读 2016-05-06 12:00:20
    Linux网络编程:原始套接字的魔力【上】 http://blog.chinaunix.net/uid-23069658-id-3280895.html 基于原始套接字编程  在开发面向连接的TCP和面向无连接的UDP程序时,我们所关心的核心问题在于数据收发...
  • Linux 套接字编程-基础总结

    千次阅读 2010-08-19 18:51:00
    这两天一直在看《linux C编程实战》网路编程一章,主要研究的是套接字编程这部分。里面的大部分程序自己都上机验证了。最后的一个综合应用,服务器/客户端 程序自己也是亲自敲进电脑的。也许敲的过程就是一种...
  • Linux原始套接字抓取底层报文

    千次阅读 2018-12-16 00:08:09
    1.原始套接字使用场景  我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据。收到的数据往往只包括应用层数据,原有的头部信息在传递...
  • Linux 网络编程——套接字的介绍

    千次阅读 多人点赞 2015-04-14 20:45:58
    套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行通信。我们可以用套接字中的相关函数来完成通信过程。 流套接字(SOCK_STREAM): 流套接字用于提供面向连接、可靠的数据...
  • 上一篇文章已经讨论了linux套接字基于TCP的客户端和服务器端编程,这片文章详细讨论linux套接字基于UDP的客户端和服务器端编程。 UDP与TCP相比要简洁很多,UDP不需要listen,accept和connect过程。*1. socket函数...
  • 背景知识阻塞和非阻塞对于一个套接字的 I/O通信,它会涉及到两个系统对象,一个是调用这个IO的进程或者线程,另一个就是系统内核。比如当一个读操作发生时,它会经历两个阶段: ①等待数据准备 (Waiting for the ...
  • 前面我们已经将了TCP/UDP的基本知识,还说了并发服务器与迭代服务器的区别,我们大致了解大多数TCP服务器是并发的,大多数UDP服务器是迭代的 ,即我们在进行数据传送的时候,...TCP套接字编程模型图我们首先看一下TCP客

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 79,825
精华内容 31,930
关键字:

linux套接字端口

linux 订阅