Linux网络编程:客户端/服务器的简单实现

一、 Socket的基本知识


1. socket功能

Socket层次

Socket实质上提供了进程通信的端点,进程通信之前,双方必须首先各自创建一个端点,否则是没有办法建立联系并相互通信的。

每一个Socket都一个半相关描述:

{协议, 本地地址, 本地端口}

完整的Socket的描述:

{协议, 本地地址, 本地端口, 远程地址, 远程端口}

2. Socket工作流程

面向连接(TCP)的Socket工作流程

UDP的socket工作流程

l 服务器端

首先,服务器应用程序用系统调用socket()来创建一个socket,它是系统分配给该服务器进程的类似文件描述符的资源,不能与其他进程共享。

接下来,需要给socket绑定,本地socket绑定的是Linux文件系统中的文件名,一般放在/tmp或者/usr/tmp目录中。对于网络socket,要和客户连接的特定网络相关的服务标示符(端口号或者访问点)。可以使用系统调用bind()来绑定socket,然后服务器进程就用listen()创建一个队列将客户的连接存入队列,再使用accept()接收客户的连接。

服务器调用accept()时会创建一个和原有的socket不同的新socket。这个新socket只用于与这个特定的客户进行通信,而原socket保留下来继续处理来自其他客户的连接。

l 客户端

客户端是首先调用socket()创建一个未绑定的socket,然后将服务器的socket作为一个地址调用connect()与服务器建立连接。

3. 套接字属性

l 套接字的域(domain)

l 套接字的类型(type)

套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字。

流式套接字(SOCK_STREAM)

流式的套接字可以提供可靠的、面向连接的通讯流。如果你通过流式套接字发送顺序的数据:“1”、“2”,那么数据到达的顺序也是“1”、“2”。流式套接字在AF_INET域中使用TCP协议来保证数据传输的正确性及顺序性。TCP是TCP/IP协议的前半部分,IP只处理网络路由。

数据报套接字(SOCK_DGRAM)

数据报协议定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。它使用UDP/IP协议。UDP将数据打包,贴上IP地址,然后发送。这个过程不需要建立连接。

原始套接字

原始套接字主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是没有流式套接字和数据报套接字使用方便,一般的程序也不涉及到原始套接字。

4. 套接字地址

AF_INET与AF_UNIX域的套接字地址结构不相同,分别为struct sockaddr_in, struct sockaddr_un。

l AF_INET地址结构

#include <netinet/in.h>

struct sockaddr_in

{

short int sin_family; /* AF_INET */

unsigned short int sin_port; /* Port Numbers*/

struct in_addr sin_addr; /* Internet Address */

}

IP地址结构in_add定义为:

struct in_addr

{

unsigned long int s_addr; /* IP地址是四个字节的一个32位值 */

}

l AF_UNIX地址结构

#include <sys/un.h>

struct sockaddr_un

{

sa_family_t sun_family; /* AF_UNIX */

char sun_path[]; /* pathname */

}

在当前的Linux系统中,由X/Open规范定义的类型sa_family_t在头文件sys/un.h中声明,它是短整数类型。另外sun_path指定的路径名长度也是有限制的(Linux规定的是108个字符)。

二、 转换函数

1. 主机字节序和网络字节序

因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,低位在后,有的系统是的低位在前,高位在后),而网络传输的字节序需要统一。所以,对于主机字节序和网络字节序不同的机器,就一定要对数据进行转换(例如IP地址的表示和端口号的表示)。如果主机字节序和网络字节序相同,也要调用转换函数,真正转换
还是不转换由系统函数自己决定。

转换函数:

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong); /* host to network long */

unsigned short int htons(unsinged short int hostshort); /* host to network short */

unsigned long int ntohl(unsigned long int netlong); /* network to host long */

unsigned short int ntohs(unsigned short int netshort); /* network to host short */

这些函数将16位和32位整数在主机字节序和标准的网络字节序之间进行转换。“h”代表主机“host”,“n”代表网络“network”,“l”代表“long”,“s”代表“short”。

三、 socket系统调用

1. 创建套接字socket()

socket()系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字。

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

创建的套接字是一条通信线路的一个端点,domain参数指定协议族,type参数指定这个套接字的通信类型,protocol参数指定使用的协议。

最常用的套接字域是AF_UNIX和AF_INET,前者用于通过UNIX和Linux文件系统实现的本地套接字,后者用于UNIX网络套接字。AF_INET套接字可以用于通过包括互联网在内的TCP/IP网络进行通信的程序。

参数type指定这个socket的通信类型,protocol参数指定使用的协议。通信所需的协议一般是由socket类型来决定,通常不需要进行选择。只有当需要选择的时候,才会用到protocol参数。将protocol参数设置为0表示使用默认协议。

socket返回一个描述符,类似于文件描述符。这个描述符可以用于read(),write()等系统调用来连接另一个socket。

实例:创建socket,AF_INET,SOCK_STREAM。

server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

2. 绑定socket

在调用socket()获得描述符之后,需要对该套接字进行绑定。AF_UNIX套接字会关联到一个文件系统的路径名,而AF_INET套接字会关联到一个IP端口号。

#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address, size_t address_len);

bind将参数address中的地址分配给与文件描述符socket关联的未命名套接字。address_len传递地址结构体的长度。
地址的长度取决于地址的类型。bind系统调用需要将struct sockaddr_in或struct sockaddr_un指针转换成struct
sockaddr *类型。

bind在调用成功时返回0, 失败是返回-1并设置errno。


















EBADF

文件描述符无效

ENOTSOCK

文件描述符对应的不是一个socket

EINVAL

文件描述符对应的是一个已经绑定的socket

EADDRNOTAVAIL

地址不可用

EADDRINUSE

地址已经绑定了一个socket

表2 errno值

AF_UNIX还有一些错误代码









EACCESS

权限不足,不能创建文件系统中的路径名

ENOTDIR, ENAMETOOLONG

文件名不符合要求

表3 AF_UNIX部分errno值

实例:

bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

3. 创建套接字队列

为了能够在套接字上接受进入的链接,服务器要建立一个队列来保存未处理的请求。

#include <sys/socket.h>

int listen(int socket, int backlog);

参数backlog设置队列中可以容纳的未处理连接的最大个数。超过这个数字后,剩下的连接会被拒绝。backlog常用值为5……

listen函数会在成功时返回0,失败时返回-1,错误代码包括EBADF,EINVAL和ENOTSOCK。

实例:

listen(server_sockfd, 5);

4. 接受连接

一旦服务器程序创建并绑定了socket之后,他就可以通过用accept()来等待客户建立对该socket的连接。

#inculde <sys/socket.h>

int accept(int socket, struct sockaddr *address, size_t *address_len);

accept只有当有客户程序尝试连接到由socket参数指定的socket上时才返回。accept将创建一个新socket来与该客户进行通信,将该socket描述符作为返回值。之后的读写动作都关联到该socket描述符上。

参数socket所关联的套接字必须首先已经被bind绑定,而且有listen为其分配连接队列。参数address表示客户的地址,如果不关心客户的地址值可设为空指针。

如果socket没有未处理的连接accept将阻塞直到队列中有未处理的连接。可以通过设置O_NONBLOCK来改变。实例:

int flags = fcntl(socket, F_GETFL, 0);

fcntl(socket, F_SETFL, O_NONBLOCK | flags);

发生错误时,accept会返回-1。

5. 请求连接

客户程序通过与服务器监听套接字之间绑定的方法连接到服务器。

#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address, size_t address_len);

参数socket指定的套接字将连接到参数address指定的服务器的socket上。

成功时,connect返回0,失败返回-1。

如果连接不能立刻建立,connect将阻塞到超时时间,超过超时时间连接将被放弃,连接失败。

6. 关闭socket

可以通过close()来终止服务器与客户端的socket连接。

#include <unistd.h>

int close(int socket);

7. 发送数据send()

send()同样可以发送数据,与write()不同的是,send()只能用于socket数据的发送。

#include <sys/socket.h>

int send(int socket, const void *buff, int len, int flags)

参数中,buff指向要发送的数据,len为要发送数据的长度, flags一般为0。

成功时send返回发送的字节数,失败返回-1。

8. 接收数据recv()

与send()相同,recv()也只能用于socket的数据发送。

#include <sys/socket.h>

int recv(int socket, void *buf, int len, unsigned int flags)

buf指向存放接收数据的缓冲区,len为数据长度,flags一般为0。

成功时recv()返回接收的字节数,失败时返回-1。

9. 发送数据sendto()

sendto需要带上发送目的地的地址信息,可以用于UDP通讯的实现,TCP中也可以使用sendto()。

#include <sys/socket.h>

int sendto(int socket, const void *buff, int len, unsigned int flags, const struct sockaddr *addr_to, int addr_len)

buff指向要发送的数据,len为要发送的数据的长度,flags一般为0,addr_to携带发送目的IP的信息,addr_len是地址信息的长度。

成功时,sendto返回发送的字节数,失败返回-1。

10.接收数据recvfrom()

recvfrom()与sendto配套使用,实现数据的收发。

#include <sys/socket.h>

int recvfrom(int socket, const void *buff, int len, unsigned int flags, const struct sockaddr *addr_from, int addr_len)

buff指向接收数据的缓冲区,len为数据长度,flags一般为0, addr_from存放数据来源的IP地址,addr_len为地址信息的长度。

recvfrom成功时返回接收的字节数,失败返回-1。

四、 阻塞

connect(),recv()都是阻塞性函数,当需求的资源没有准备好的时候,调用函数的进程将进入休眠状态,这样就无法处理I/O多路复用的情况了。

解决这个问题的方法与普通的文件操作相同:使用fcntl()或者select()函数。相比较fcntl(),select()函数还可以设置等待时间,功能更为强大。

----<end>----

Linux网络编程:客户端/服务器的简单实现,布布扣,bubuko.com

时间: 05-15

Linux网络编程:客户端/服务器的简单实现的相关文章

Linux网络编程客户\服务器设计范式

1.前言 网络编程分为客户端和服务端,服务器通常分为迭代服务器和并发服务器.并发服务器可以根据多进程或多线程进行细分,给每个连接创建一个独立的进程或线程,或者预先分配好多个进程或线程等待连接的请求.今天探讨三种设计范式 (1)迭代服务器 (2)并发服务器,为每个客户请求创建一个进程或线程 (3)预先分配子进程或线程,每个子进程或线程调用accept 3.测试用例: 客户端代码: 1 #include <sys/wait.h> 2 #include <string.h> 3 #inc

Linux 网络编程——并发服务器的三种实现模型

服务器设计技术有很多,按使用的协议来分有 TCP 服务器和 UDP 服务器,按处理方式来分有循环服务器和并发服务器. 循环服务器与并发服务器模型 在网络程序里面,一般来说都是许多客户对应一个服务器(多对一),为了处理客户的请求,对服务端的程序就提出了特殊的要求. 目前最常用的服务器模型有: ·循环服务器:服务器在同一时刻只能响应一个客户端的请求 ·并发服务器:服务器在同一时刻可以响应多个客户端的请求 UDP 循环服务器的实现方法 UDP 循环服务器每次从套接字上读取一个客户端的请求 -> 处理

linux网络编程echo服务器

echo_server #include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <sys/socket.h>#include <www.qixoo.qixoo.com/sys/types.h>#include <signal.h>#include <memory.h>#include <errno.h>#include <netin

Linux网络编程入门 (转载)

http://www.cnblogs.com/RascallySnake/archive/2012/01/04/2312564.html (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端        在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一        个地方获取文件

Linux网络编程入门

(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍 客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端 在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一 个地方获取文件的时候,是我们的ftp程序主动同外面进行通信(获取文件), 所以这个地方我们的ftp程序就是客户端程序. 服务端 和客户端相对应的程序即为服务端程序.被动的等待外面的程序来和自己通

[转] - Linux网络编程 -- 网络知识介绍

(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端        在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一        个地方获取文件的时候,是我们的ftp程序主动同外面进行通信(获取文件), 所以这个地方我们的ftp程序就是客户端程序. 服务端        和客户端相

Linux网络编程(简单的时间获取服务器)

1.一个简单的服务器时间获取程序 服务器和客户端采用UDP通信的方式,来编写一个简单的时间获取应用. 把过程大致理顺一下,首先是服务器端的编写,使用的是迭代的方式,没有并发 先创建一个socket而后bind服务器,绑定之后就可以创建一个循环来接收和发送 信息了,以达到和客户端之间的通信. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include

《Linux高性能服务器编程》学习总结(五)——Linux网络编程基础API

第五章      Linux网络编程基础API 对于网络编程,首先要了解的就是字节序的问题,字节序分为主机字节序和网络字节序,主机字节序又称小端字节序,是低字节存放在地地址,而网络字节序又称大端字节序,是低字节放在高地址.当数据在不同的机器上传播时,就需要统一字节顺序以保证不出现错误.在发送数据前,先将需要转变的数据转成网络字节序再发送,接收时先转成主机字节序再处理,要特别注意的是,即使是本机的两个进程通信,也要考虑字节序的问题,比如JAVA的虚拟机就使用大端字节序.使用如下代码可以查看本机的字

Linux网络编程——tcp并发服务器(poll实现)

想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程--I/O复用之poll函数> 代码: #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #include <sys/socket.h> #incl