分享
 
 
 

BSD套接口篇(上)(翻译)

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

Linux网络编程,第一部分

BSD套接口篇

——这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇。就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议。在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等。

这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 。在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题。而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/distributed objects进行开发。

一、TCP/IP的基础介绍

TCP/IP协议族允许两个运行在同一台电脑或者由网络连接在一起的两台电脑上的程序进行通讯。这个协议族是专门为了在不可靠的网络上进行通讯设计的。TCP/IP允许两个基本的操作模式——面向连接的可靠的传输(指TCP)和无连接的(connectionless)不可靠的传输(UDP)。下图举例说明了TCP/IP协议族的分层结构。

TCP提供带有对上层协议透明的中继功能的,顺序的,可靠的,双向的(bi-directional),以连接为基础的字节传输流。TCP将你的信息分割成数据报(不大于64kb)并保证所有的数据报无误的按照顺序都到达目的地。由于以连接为基础,所以一个虚拟连接必须在一个网络实体(network entity)和另一个之间进行通信前建立。UDP相反则提供一个(非常快的)无连接的不可靠消息传输(消息的大小是一个确定的最大长度)。

为了使程序间可以相互通信,不论他们是在同一个机器(通过loopback接口)还是不同主机,每一个程序都必须有独立的地址。

TCP/IP地址由两部分组成——用来辨别机器的IP地址和用来辨别在那台机器上的特定程序的端口地址。

地址可以是点分(dotted-quad)符号形式的(如,127.0.0.1)或者是主机名形式的(如,www.csdn.net)。系统可以使用/etc/hosts或DNS域名服务(如果可以获得的话)进行主机名到点分符号地址(也就是IP地址)的转换。

端口从1号开始编号。1和IPP0RT_RESERVED(在/usr/include/netinet/in.h中定义,通常为1024)之间的段口号保留给系统使用(也就是说,你必须以root的身份建立一个网络服务来绑定这部分的端口)。

最简单的网络程序大都用的客户-服务器模型。一个服务进程等待一个客户进程连接他。当连接建立时,服务器代表客户执行特定的任务,通常这这以后连接就中断了。

二、使用BSD套接口界面

最通行的TCP/IP编程方法就是使用BSD套接口界面编程。通过它,网络端点(network endpoints)(IP地址和端口地址)以套接口(sockets)的形式出现。

这套套接口IPC(interprocess communication,进程间通讯)设施(从4.2BSD开始引入)的设计是为了能让网络程序的设计能够独立于不同的底层通信设施。

1、建立一个服务器程序

要使用BSD界面建立一个服务器程序,你必须通过以下步骤:

1。通过函数socket()建立一个套接口

2。通过函数bind()绑定一个地址(IP地址和端口地址)。这一步确定了服务器的位置,使客户端知道如何访问。

3。通过函数listem()监听(listen)端口的新的连接请求。

4。通过函数accept()接受新的连接。

通常,维护代表了客户的请求可能需要花费相当长的一段时间。在处理一个请求时,接收和处理新的请求也应该是高效的。达到这种目的的最通常的做法是让服务器通过fork()函数拷贝一份自己的进程来接受新的连接。(下图:客户服务器代码模型的表示形式)

以下的例子显示了服务器是如何用C实现的:

/*

* Simple "Hello, World!" server

* Ivan Griffin (ivan.griffin@ul.ie)

*/

/* Hellwolf Misty translated */

#include /* */

#include <stdio.h> /* */

#include <stdlib.h> /* exit() */

#include <string.h> /* memset(), memcpy() */

#include <sys/utsname.h> /* uname() */

#include <sys/types.h>

#include <sys/socket.h> /* socket(), bind(),

listen(), accept() */

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#include <unistd.h> /* fork(), write(), close() */

/*

* constants

*/

const char MESSAGE[] = "Hello, World!\n";

const int BACK_LOG = 5;

/*

*程序需要一个命令行参数:需要绑定的端口号

*/

int main(int argc, char *argv[])

{

int serverSocket = 0,

on = 0,

port = 0,

status = 0,

childPid = 0;

struct hostent *hostPtr = NULL;

char hostname[80] = "";

struct sockaddr_in serverName = { 0 };

if (2 != argc)

{

fprintf(stderr, "Usage: %s \n",

argv[0]);

exit(1);

}

port = atoi(argv[1]);

/ *

*socket()系统调用,带有三个参数:

* 1、参数domain指明通信域,如PF_UNIX(unix域),PF_INET(IPv4),

* PF_INET6(IPv6)等

* 2、type指明通信类型,最常用的如SOCK_STREAM(面向连接可靠方式,

* 比如TCP)、SOCK_DGRAM(非面向连接的非可靠方式,比如UDP)等。

* 3、参数protocol指定需要使用的协议。虽然可以对同一个协议

* 家族(protocol family)(或者说通信域(domain))指定不同的协议

* 参数,但是通常只有一个。对于TCP参数可指定为IPPROTO_TCP,对于

* UDP可以用IPPROTO_UDP。你不必显式制定这个参数,使用0则根据前

* 两个参数使用默认的协议。

*/

serverSocket = socket(PF_INET, SOCK_STREAM,

IPPROTO_TCP);

if (-1 == serverSocket)

{

perror("socket()");

exit(1);

}

/*

* 一旦套接口被建立,它的运作机制可以通过套接口选项(socket option)进行修改。

*/

/*

* SO_REUSEADDR选项的设置将套接口设置成重新使用旧的地址(IP地址加端口号)而不等待

* 注意:在Linux系统中,如果一个socket绑定了某个端口,该socket正常关闭或程序退出后,

* 在一段时间内该端口依然保持被绑定的状态,其他程序(或者重新启动 的原程序)无法绑定该端口。

*

* 下面的调用中:SOL_SOCKET代表对SOCKET层进行操作

*/

on = 1;

status = setsockopt(serverSocket, SOL_SOCKET,

SO_REUSEADDR,

(const char *) &on, sizeof(on));

if (-1 == status)

{

perror("setsockopt(...,SO_REUSEADDR,...)");

}

/* 当连接中断时,需要延迟关闭(linger)以保证所有数据都

* 被传输,所以需要打开SO_LINGER这个选项

* linger的结构在/usr/include/linux/socket.h中定义:

* struct linger

* {

* int l_onoff; Linger active

* int l_linger; How long to linger

* };

* 如果l_onoff为0,则延迟关闭特性就被取消。如果非零,则允许套接口延迟关闭。

* l_linger字段则指明延迟关闭的时间

*/

{

struct linger linger = { 0 };

linger.l_onoff = 1;

linger.l_linger = 30;

status = setsockopt(serverSocket,

SOL_SOCKET, SO_LINGER,

(const char *) &linger,

sizeof(linger));

if (-1 == status)

{

perror("setsockopt(...,SO_LINGER,...)");

}

}

/*

* find out who I am

*/

status = gethostname(hostname,

sizeof(hostname));

if (-1 == status)

{

perror("gethostname()");

exit(1);

}

hostPtr = gethostbyname(hostname);

if (NULL == hostPtr)

{

perror("gethostbyname()");

exit(1);

}

(void) memset(&serverName, 0,

sizeof(serverName));

(void) memcpy(&serverName.sin_addr,

hostPtr->h_addr,

hostPtr->h_length);

/*

*h_addr是h_addr_list[0]的同义词,

* h_addr_list是一组地址的数组

*长度为4(byte)代表一个IP地址的长度

*/

/*

* 为了使服务器绑定本机所有的IP地址,

* 上面一行代码需要用下面的代码代替

* serverName.sin_addr.s_addr=htonl(INADDR_ANY);

*/

serverName.sin_family = AF_INET;

/* htons:h(host byteorder,主机字节序)

* to n(network byteorder,网络字节序

* s(short类型)

*/

serverName.sin_port = htons(port);

/* 在一个地址(本例中的serverSocket)被建立后

* 它就应该被绑定到我们获得的套接口。

*/

status = bind(serverSocket,

(struct sockaddr *) &serverName,

sizeof(serverName));

if (-1 == status)

{

perror("bind()");

exit(1);

}

/* 现在套接口就可以被用来监听新的连接。

* BACK_LOG指定了未决连接监听队列(listen queue for pending connections)

* 的最大长度。当一个新的连接到达,而队列已满的话,客户就会得到连接拒绝错误。

* (这就是dos拒绝服务攻击的基础)。

*/

status = listen(serverSocket, BACK_LOG);

if (-1 == status)

{

perror("listen()");

exit(1);

}

/* 从这里开始,套接口就开始准备接受请求,并为他们服务。

* 本例子是用for循环来达到这个目的。一旦连接被接受(accpepted),

* 服务器可以通过指针获得客户的地址以便进行一些诸如记录客户登陆之类的

* 任务。

*/

for (;;)

{

struct sockaddr_in clientName = { 0 };

int slaveSocket, clientLength =

sizeof(clientName);

(void) memset(&clientName, 0,

sizeof(clientName));

slaveSocket = accept(serverSocket,

(struct sockaddr *) &clientName,

&clientLength);

if (-1 == slaveSocket)

{

perror("accept()");

exit(1);

}

childPid = fork();

switch (childPid)

{

case -1: /* ERROR */

perror("fork()");

exit(1);

case 0: /* child process */

close(serverSocket);

if (-1 == getpeername(slaveSocket,

(struct sockaddr *) &clientName,

&clientLength))

{

perror("getpeername()");

}

else

{

printf("Connection request from %s\n",

inet_ntoa(clientName.sin_addr));

}

/*

* Server application specific code

* goes here, e.g. perform some

* action, respond to client etc.

*/

write(slaveSocket, MESSAGE,

strlen(MESSAGE));

/* 也可以使用带缓存的ANSI函数fprint,

* 只要你记得必要时用fflush刷新缓存

*/

close(slaveSocket);

exit(0);

default: /* parent process */

close(slaveSocket);/* 这是一个非常好的习惯

* 父进程关闭子进程的套接口描述符

* 正如上面的子进程关闭父进程的套接口描述符。

*/

}

}

return 0;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有