您的位置:首页 > 理论基础 > 计算机网络

Linux应用程序之socket网络编程(非常详细)

2017-05-11 16:45 211 查看
http://bbs.csdn.net/topics/390822770/
http://bbs.csdn.net/topics/390822770/
网络编程—服务器/客户机

1、 计算机网络体系结构模式

所有的网络通信方式分为两种:线路交换和包交换;线路交换是在数据传输时,在发送

端和接受端建立一条特定的线路连接,数据就在这条线路上传输,电话就是采用这种方式。

计算机网络则采用的是包交换的方式,数据的发送端将要发送的数据分为块,每个数据块经过处理形成一个数据包,其中包括有接受端的地址等信息,每个包都是单独传输。

2、 服务器端软件设计算法

⑴面向连接的服务器算法

① 服务器接受客户端的连接请求;

② 通过这个连接进行相关的通信;

③ 服务器在完成交互后关闭请求。

面向连接的设计方法要求每个连接都要创建一个套接口(socket),这样当请求客户过多

的时候,服务器可能因为资源耗尽而停止运行。

⑵无连接的服务器算法

无连接的设计方法中,一个套接口可以与多个主机通信,不会出现资源耗尽的问题。但是它的可靠性相对而言较差。

⑶迭代服务器的算法

在一个时刻只能处理一个客户请求的服务器叫做迭代服务器

① 创建一个套接口,并将它绑定在众所周知的服务器端口上;

② 从套接口上取下一个连接请求,并获得该请求的套接字

③ 重复读取请求队列中的连接请求,构造一个响应,按照响应的协议向客户发回响应

④ 当与特定的客户完成交互之后关闭连接,并接受新的连接请求。

⑷并发服务器的算法

并发服务器就是就是同一时刻可以处理多个客户请求的服务器。大多数的并发服务器使

用多进程来实现并发性,一个主进程最先开始运行,使用套接口在众所周知的端口监听连接

请求,并为每一个请求创建一个服务器进程,由一个从进程来处理一个客服的通信。

① 创建UDP套接口并与众所周知的端口绑定;

② 调用recefrom()来接受客户端的请求,并创建一个新的进程来处理响应;



3、 socket基础

socket接口是TCP/IP网路的API,socket接口定义了许多函数或者例程,程序员可以

用它们来开发网络应用程序。网络socket数据传输是一种特殊的I/O,socket也是一种文件

描述符。Socket也具有一个类似打开文件的函数调用socket(),该函数返回一个socket

文件描述符句柄。随后连接的建立,数据的传输等操作都是通过该句柄来实现的。

⑴套接字的基本类型

① 数据流套接字(SOCK_STREAM):数据流套接字是一种面向连接的套接字,采用的面

向连接的TCP服务应用。Eg telnet 、www浏览器使用的Http协议

② 数据报套接字(DGRAM):数据报套接字是一种无连接的套接字,对应于无连接的UDP

服务应用。

4、socket网络编程

Linux采用了socket套接字,与套接字相关的函数包含在头文件sys/socket.h中。

Socket相当网络上的通信节点,即IP和端口号组成。

网络程序的设计可以采用TCP和UDP两种协议,TCP是一种可靠的、面向连接的协

议,而UDP是一种不可靠的无连接的协议。

4.1采用TCP协议进行网络程序设计

⑴在进行网络程序设计的时候,一般按照客户端和服务器端分别进行设计,其设计流程

是不一样的,下图为TCP网络程序设计的设计流程。



图4-1 TCP程序设计流程

⑵ socket相关的函数

①socket()函数

表头文件 #include<sys/socket.h>

定义函数: int socket(int family,int type,intprotocol);

函数说明: socket() 函数用来生成一个套接口描述字,也称为套接字,指定协议族和套接口。参数: family 指定协议族(在网络程序中只能设置为:AF_INET), type 指明套接字的类型(SOCK_STREAM为数据流套接字,采用TCP建立连接,而SOCK_DGRAM数据报套接字,采用的是UDP协议),而 protocol 一般为 0

③ bind()函数

表头文件 #include<sys/types.h> #include<sys/socket.h>

定义函数 int bind(int sockfd, (struct sockaddr *) my_addr, int addrlen);

函数说明 bind() 用于将本机的地址与创建的套接字绑定。其中sockfd为套接字号,

my_addr为指向地址结构的指针,addrlen为地址结构sockaddr_in的长度。成功返回0,失

败返回-1,把错误原因放在在errno中。

与套接字绑定的地址因为协议族的不同(family)而不同,在网络程序中因为family只能取AF_INET,故sockaddr的结构定义为:

struct sockaddr_in{

sa_family_t sin_family;

unsigned short int sin_port;/*端口号*/

struct in_addr sin_addr; /*IP地址*/

}

struct in_addr

{

uint32_t s_addr;

};

由于IP地址为xx.xx.xx.xx的字符形式地址结构体中的IP地址为无符号整型,因此需要

采用下面的函数进行转换:

unsigned long inet_addr(const char * string);

而端口号的获取需要使用下面的两个函数,因为一般intel架构的处理器采用的是小端格式,而motorola和sun公司的机器采用的是大端格式,为了消除这个差别,必须采用下面的两个函数设置端口号;

unsigned long htonl(unsigned long hostlong);

unsigned long htons(unsigned long hostshort);

eg struct sockaddr_in adr_srvr;

adr_srvr.sin_addr.s_addr=inet_addr(“192.168.1.105”);

adr_srvr.sin_port=htons(8000);

④ listen()函数

头文件:#include<sys/socket.h>

函数原型:int listen(int sockfd, int backlog);

在套接字创建之后,它是一个主动连接的套接字,也就是此时系统假设用户会对这个套

接字调用connect函数,期待它主动与其它进程连接,然而在服务器编程中,用户希望这

个套接字可以接受外来的连接请求,也就是被动等待用户来连接。因此需要通过listen()

函数来使主动套接口变为被动套接口,从而使得一个进程可以接受其它进程的连接请求变为

服务器进程; 参数backlog用来指定最大的连接数,一般设定为5;

⑤accept()函数

头文件:#include<sys/socket.h>

函数原型:int accept(int sockfd, struct sockaddr_in *addr, int addrlen);

当服务器端接受到客户端的连接请求的时候,会把连接请求方在连接队列中,接着用

accept()函数处理并接受队列中的请求。accept默认会阻塞进程,直到有一个客户连接

建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。"阻塞"是一个术

语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,然后程序继续;

通常"阻塞"是由循环产生的。此时我们需要区分两种套接字,一种套接字正如

accept的参数sockfd,它是监听套接字,在调用listen函数之后,一个套接字

会从主动连接的套接字变身为一个监听套接字;而accept返回是一个连接套接字,

它代表着一个网络已经存在的点点连接。

参数sockfd:

参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个

客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。

当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。

参数addr:

这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地

址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地

址不感兴趣,那么可以把这个值设置为NULL。

参数len:

如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明

addr结构所占有的字节个数。同样的,它也可以被设置为NULL。如果accept成功返回,

则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户

的通信。

⑥connect()函数

头文件:#include<sys/socket.h>

函数原型:int connect(int sockfd,struct sockaddr *addr,int addrlen);

connect函数是客户端使用的函数,当用创建一个套接口后,可以调用connect为这

个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手建立一个连

接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据。第一个

参数是socket函数返回的套接口描述字(套接字);第二和第三个参数分别是一个指向服

务器端套接口地址结构的指针和该结构的大小。

这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。

以IPv4套接口地址结构为例,它以“sockaddr_in”命名,定义在头文件<netinet/in.h>;

以下是结构体的内容:

struct in_addr {

in_addr_t s_addr;/* IPv4地址 */

};

struct sockaddr_in {

uint8_t sin_len; /* 无符号的8位整数 */

sa_family_t sin_family;

/* 套接口地址结构的地址簇,这里为AF_INET */

in_port_t sin_port; /* TCP或UDP端口 */

struct in_addr sin_addr;

char sin_zero[8];

};

⑦ send()函数

头文件:#include<sys/socket.h>

函数原型:int send(int sockfd, const char *buf, int len, int flags);

不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客

户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发

送应答。该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序

要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。

同步Socket的send函数的执行流程:

(1)send先比较待发送数据的长度len和套接字的发送缓冲的长度,该函数返回SOCKET_ERROR;

(2)如果len小于或者等于套接字的发送缓冲区的长度,那么send先检查协议是否

正在发送套接字发送缓冲区中的数据,如果是就等待协议把数据发送完,如果协议还没有开

始发送套接字发送缓冲区中的数据或者套接字发送缓冲区中没有数据,那么send就比较套

接字的发送缓冲区的剩余空间和len的大小;

(3)如果len大于剩余空间大小,send就一直等待协议把套接字发送缓冲中的数据发送完

(4)如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里

(注意并不是send把套接字发送缓冲区中的数据传到连接的另一端的,而是协议传的,s

end仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。

如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数

据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时

网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的

数据成功copy到套接字发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定

马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个S

ocket函数就会返回SOCKET_ERROR。

注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

⑧recv()函数

头文件:#include<sys/socket.h>

函数原型:int recv(int sockfd, const char FAR*buf, int len, int flags);

不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数

的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放r

ecv函数接收到的数据;第三个参数指明buf的长度;第四个参数一般置0。

同步Socket的recv函数的执行流程:

(1)recv先等待套接字发送缓冲区中的数据被协议传送完毕,如果协议在传送套接字

发送缓冲区中数据时出现网络错误,那么recv函数返回SOCKET_ERROR,

(2)如果套接字的发送缓冲区中没有数据或者数据被协议成功发送完毕后,recv先检

查套接字的接收缓冲区,如果套接字接收缓冲区中没有数据或者协议正在接收数据,那么

recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把套

接字的接收缓冲区中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,

所以 在这种情况下要调用几次recv函数才能把套接字的接收缓冲中的数据copy完。rec

v函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy

的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在

等待协议接收数据时网络中断了,那么它返回0。

注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

⑨ close()函数

头文件:#include<sys/socket.h>

函数原型:int close(int sockfd);

使用close()函数终止客户端和服务器端的连接,函数运行成功返回0,否则返回-1.

5、socket 聊天工具

5.1 服务器端的源码:

C/C++ code

?
5.2 客服端的源码:

C/C++ code

?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: