Chinaunix首页 | 论坛 | 博客
  • 博客访问: 215765
  • 博文数量: 27
  • 博客积分: 2510
  • 博客等级: 少校
  • 技术积分: 310
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-15 22:38
文章分类
文章存档

2011年(1)

2008年(26)

我的朋友

分类: LINUX

2008-05-12 16:52:41

套接口编程基础  
参考资料地址http://blog.163.com/hao_ff/blog/ 
IPv4 套接口地址结构
struct in_address {
in_addr_t s_addr ;
}
// 32bits IPv4 地址
//网络字节顺序 Network Byte Order

struct sockaddr_in{
unit8_t sin_len; //长度成员, 无需设置
sa_family_t sin_family; //套接口结构地址族,AF_INET
in_port_t sin_port; //16位TCP 或 UDP 端口号
struct in_addr sin_addr; //32位TCP 或 UDP 端口地址
char sin_zero[8]; //未用
}
//in: internet
//s: socket

通用套接口地址结构
套接口地址结构仅在给定主机上使用。结构中的某些成员(IP地址和端口号)用在不同主机

间的通信中,但结构本身并不参与通信.

当作为参数传递给任一个套接口函数时,套接口地址结构总是通过指针来传递,但通过指

针来取得此参数的套接口函数必须处理来自所支持的任何协议族的套接口地址结构.

通用套接口结构

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

通用的套接口地址结构的用途:给指向持定于协议的地址结构的指针转换类型。


--------------------------------------------------------------------------------

强制类型转换
函数的调用:

将指向特定于协议的套接口地址结构的指针类型-> 指向通用套接口地址结构的指针。

函数原型:
int connect( int, struct sockaddr *, socklen_t)

..............
struct sockaddr-in servaddr;

..................

connect(sockfd,(sturct sockaddr *) &servaddr, sizeof(servaddr));
.....................


--------------------------------------------------------------------------------

字节排序函数
一个16位整数,它由2个字节组成。

内存中存储这两个字节有两种方法:

小端字节序:低序字节存储在起始地址

大端字节序:高序字节存储在起始地址

网际协议必须指定一个网络字节序(Network Byte Order)

主机字节序和网络字节序的转换函数:

#include
unit16_t htons(uint16_t host16bitvalue);

unit32_t htons(uint32_t host32bitvalue);

unit16_t ntohs(uint16_t net16bitvalue);

unit32_t ntohs(uint32_t net32bitvalue);

h : host
n : network
s : short (16 bits)
l : long (32 bits)


--------------------------------------------------------------------------------

字节/字节流操纵函数
void bzero(void *dest,size_t nbytes);//清零

void bcopy(const void *src,void *dest,siz_t nbytes);

int bcmp(const void *ptr1,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);

it memcmp(const void *ptr1,const void *ptr2,size_t nbytes);

返回:0——相同,非0——不相同

字节流读写函数

ssize_t readn(int filedes, void * buff, size_t nbytes);

ssize_t writen(int filedes, const void *buff, size_t nbytes);

ssize_t readline(intfiledes, void *buff, size_t maxlen);


--------------------------------------------------------------------------------

地址转换函数
#include

int inet_aton(const char *strptr,struct in_addr *addrptr);

返回:1——串有效,0——串出错

in_addr_t inet_addr(const char *strptr);

返回:若成功,返回32位二进制的网络字节序地址;若出错,则返回INADDR_NONE

char *inet_ntoa (struct in_addr inaddr);

返回:指向点分十进制数串指针

ine-aton 将 strptr 所指的C字符串转换成32位的网络字节序二进制值并通过指针

addrptr来存储。如果成功返回1,否则返回0。

inet-addr 将 strptr 所指的C字符串转换成32位的网络字节序二进制值并通过涵数值返回。

函数inet-ntoa将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。

如: 202.116.34.194.4000 (IP:202.116.34.194 端口4000)

可用于 IPv4 和 IPv6的函数

int inet_pton(int family,const char *strptr,void *addrptr);

const char *inet_ntop(int family.const void *addrptr,char *strptr,size_t len);

p: presentation 地址的表示 202.116.34.194

n: numeric 数值格式 16bits/32bits integer


family : AF_INET 或 AF_INET6
Socket编程中select()的妙用
本文出自: 作者: (2001-09-13 09:00:00)
发信站: 华南网木棉站 (Tue Aug  4 15:43:41 1998), 转信

【 原文由 cpu 所发表 】
 
用过 WinSock API 网友们知道:WinSock 编程中有一很方便的地方便是其
息驱动机制,不管是底层 API 的 WSAAsyncSelect() 还是 MFC 的异步Socket类:
CAsyncSocket,都提供了诸如 FD_ACCEPT、FD_READ、FD_CLOSE 之类的消息
供编程人员捕捉并处理。FD_ACCEPT 通知进程有客户方Socket请求连接,
FD_READ通知进程本地Socket有东东可读,FD_CLOSE通知进程对方Socket已
关闭。那么,BSD Socket 是不是真的相形见拙呢?
 
非也! 'cause cpu love unix so.
 
BSD UNIX中有一系统调用芳名select()完全可以提供类似的消息驱动机制。
cpu郑重宣布:WinSock的WSAAsyncSeclet()不过是此select()的fork版!
 
bill也是fork出来的嘛,xixi.
 
select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他
文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,
当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执
行了select()的进程哪一Socket或文件可读,下面具体解释:
 
#include 
#include 
#include 
 
int select(nfds, readfds, writefds, exceptfds, timeout)
int nfds;
fd_set *readfds, *writefds, *exceptfds;
struct timeval *timeout;
 
ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件
      中的最大文件号加一。
readfds:select监视的可读文件句柄集合。
writefds: select监视的可写文件句柄集合。
exceptfds:select监视的异常文件句柄集合。
timeout:本次select()的超时结束时间。(见/usr/sys/select.h,
        可精确至百万分之一秒!)
 
当readfds或writefds中映象的文件可读或可写或超时,本次select()
就结束返回。程序员利用一组系统提供的宏在select()结束时便可判
断哪一文件可读或可写。对Socket编程特别有用的就是readfds。
几只相关的宏解释如下:
 
FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否
                                可读写,>0表示可读写。
(关于fd_set及相关宏的定义见/usr/include/sys/types.h)
 
这样,你的socket只需在有东东读的时候才读入,大致如下:
 
...
int     sockfd;
fd_set  fdR;
struct  timeval timeout = ..;
...
for(;;) {
        FD_ZERO(&fdR);
        FD_SET(sockfd, &fdR);
        switch (select(sockfd + 1, &fdR, NULL, &timeout)) {
                case -1:
                        error handled by u;
                case 0:
                        timeout hanled by u;
                default:
                        if (FD_ISSET(sockfd)) {
                                now u read or recv something;
                                /* if sockfd is father and 
                                server socket, u can now
                                accept() */
                        }
        }
}
 
所以一个FD_ISSET(sockfd)就相当通知了sockfd可读。
至于struct timeval在此的功能,请man select。不同的timeval设置
使使select()表现出超时结束、无超时阻塞和轮询三种特性。由于
timeval可精确至百万分之一秒,所以Windows的SetTimer()根本不算
什么。你可以用select()做一个超级时钟。
 
FD_ACCEPT的实现?依然如上,因为客户方socket请求连接时,会发送
连接请求报文,此时select()当然会结束,FD_ISSET(sockfd)当然大
于零,因为有报文可读嘛!至于这方面的应用,主要在于服务方的父
Socket,你若不喜欢主动accept(),可改为如上机制来accept()。
 
至于FD_CLOSE的实现及处理,颇费了一堆cpu处理时间,未完待续。
 
--
讨论关于利用select()检测对方Socket关闭的问题:
 
仍然是本地Socket有东东可读,因为对方Socket关闭时,会发一个关闭连接
通知报文,会马上被select()检测到的。关于TCP的连接(三次握手)和关
闭(二次握手)机制,敬请参考有关TCP/IP的书籍。
 
不知是什么原因,UNIX好象没有提供通知进程关于Socket或Pipe对方关闭的
信号,也可能是cpu所知有限。总之,当对方关闭,一执行recv()或read(),
马上回返回-1,此时全局变量errno的值是115,相应的sys_errlist[errno]
为"Connect refused"(请参考/usr/include/sys/errno.h)。所以,在上
篇的for(;;)...select()程序块中,当有东西可读时,一定要检查recv()或
read()的返回值,返回-1时要作出关断本地Socket的处理,否则select()会
一直认为有东西读,其结果曾几令cpu伤心欲断针脚。不信你可以试试:不检
查recv()返回结果,且将收到的东东(实际没收到)写至标准输出...
在有名管道的编程中也有类似问题出现。具体处理详见拙作:发布一个有用
的Socket客户方原码。
 
至于主动写Socket时对方突然关闭的处理则可以简单地捕捉信号SIGPIPE并作
出相应关断本地Socket等等的处理。SIGPIPE的解释是:写入无读者方的管道。
在此不作赘述,请详man signal。
 
以上是cpu在作tcp/ip数据传输实验积累的经验,若有错漏,请狂炮击之。
 
唉,昨天在hacker区被一帮孙子轰得差点儿没短路。ren cpu(奔腾的心) z80
 
补充关于select在异步(非阻塞)connect中的应用,刚开始搞socket编程的时候
我一直都用阻塞式的connect,非阻塞connect的问题是由于当时搞proxy scan
而提出的呵呵
通过在网上与网友们的交流及查找相关FAQ,总算知道了怎么解决这一问题.同样
用select可以很好地解决这一问题.大致过程是这样的:
 
1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完
成(有的系统用FNEDLAY也可).
 
2.发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧
在进行还没有完成.
 
3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视,
如果可写,用
        getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
来得到error的值,如果为零,则connect成功.
 
在许多unix版本的proxyscan程序你都可以看到类似的过程,另外在solaris精华
区->编程技巧中有一个通用的带超时参数的connect模块.

BSD Socket 简易入门手册
本文出自:linuxdby.yeah.net 作者: 翻译:Wilbur Lang (2001-08-21 09:00:00)
类比 (什么是 socket ?)
装上你的新电话(怎样侦听?)
拨号 (如何调用 socket)
谈话(如何通过 sockets 交谈)
挂起(结束)
世界语(交流的语言很重要)
未来在你的掌握了(下一步?)

介绍
     当你进入 UNIX 的神秘世界后,立刻会发现越来越多的东西难以理解。对于大多数人来说,BSD socket 的概念就是其中一个。这是一个很短的教程来解释他们是什么、他们如何工作并给出一些简单的代码来解释如何使 用他们。

类比 (什么是 socket ?)
     socket 是进行程序间通讯(IPC)的 BSD 方法。这意味着 socket 用来让一个进程和其他的进程互通信息,就象我们 用电话来和其他的人交流一样。 用电话来比喻是很恰当的,我们在后面将一直用电话这个概念来描叙 socket 。

装上你的新电话(怎样侦听?)
     一个人要能够收到别人打给他的电话,首先他要装上一门电话。同样,你必须先建立 socket 以侦听线路。这个过程包含几个步骤。首先,你要建立一个新的 socket,就象先装上电话一样。socket() 命令就完成这个工作。 因为 sockets 有几种类型,你要注明你要建立什么类型的。你要做一个选择是 socket 的地址格式。如同电话有音频和脉冲两种形式一样,socket 有两个最重要的选项是 AF_UNIX 和 IAF_INET。AF_UNIX 就象 UNIX 路径名一样识别 sockets。这种形式对于在同 一台机器上的 IPC 很有用。而 AF_INET 使用象 192.9.200.10 这样被点号隔开的四个十进制数字的地址格式。除了机器地址以外,还 可以利用端口号来允许每台机器上的多个 AF_INET socket。我们这里将着重于 AF_INET 方式,因为他很有用并广泛使用。   
     另外一个你必须提供的参数是 socket 的类型。两个重要的类型是 SOCK_STREAM 和 SOCK_DGRAM。 SOCK_STREAM 表明数据象字符流一样通过 socket 。而 SOCK_DGRAM 则表明数据将是数据报(datagrams)的形式。我们将讲解 SOCK_STREAM sockets,他很常见并易于使用。 在建立 socket 后,我们就要提供 socket 侦听的地址了。就象你还要个电话号码来接电话一样。bind() 函数来处 理这件事情。 SOCK_STREAM sockets 让连接请求形成一个队列。如果你忙于处理一个连接,别的连接请求将一直等待到该连接处 理完毕。listen() 函数用来设置最大不被拒绝的请求数(一般为5个)。一般最好不要使用 listen() 函数。 下面的代码说明如何利用 socket()、 bind() 和 listen() 函数建立连接并可以接受数据。
/* code to establish a socket; originally from bzs@bu-cs.bu.edu */
int establish(unsigned short portnum)
{
char myname[MAXHOSTNAME+1];
int s;
struct sockaddr_in sa;
struct hostent *hp;
memset(&sa, 0, sizeof(struct sockaddr_in)); /* clear our address */
gethostname(myname, MAXHOSTNAME); /* who are we? */
hp= gethostbyname(myname); /* get our address info */
if (hp == NULL) /* we don't exist !? */
return(-1);
sa.sin_family= hp->h_addrtype; /* this is our host address */
sa.sin_port= htons(portnum); /* this is our port number */
if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */
return(-1);
if (bind(s,&sa,sizeof(struct sockaddr_in)) < 0)
{
close(s);
return(-1); /* bind address to socket */
}
listen(s, 3); /* max # of queued connects */
return(s);
}
    在建立完 socket 后,你要等待对该 socket 的调用了。accept() 函数为此目的而来。调用 accept() 如同在电话铃响后提起电话一样。Accept() 返回一个新的连接到调用方的 socket 。 下面的代码演示使用是个演示。
/* wait for a connection to occur on a socket created with establish() */
int get_connection(int s)
{
int t; /* socket of connection */
if ((t = accept(s,NULL,NULL)) < 0) /* accept connection if there is one */
return(-1);
return(t);
}

    和电话不同的是,在你处理先前的连接的时候,你还可以接受调用。为此,一般用 fork 来处理每个连接。下面的 代码演示如何使用 establish() 和 get_connection() 来处理多个连接。
#include /* obligatory includes */
#include
#include
#include
#include
#include
#include
#include
#include

#define PORTNUM 50000 /* random port number, we need something */

void fireman(void);
void do_something(int);
main()
{
int s, t;
if ((s= establish(PORTNUM)) < 0)
{
/* plug in the phone */
perror("establish");
exit(1);
}
signal(SIGCHLD, fireman); /* this eliminates zombies */
for (;;)
{
/* loop for phone calls */
if ((t= get_connection(s)) < 0)
{
/* get a connection */
if (errno == EINTR) /* EINTR might happen on accept(), */
continue; /* try again */
perror("accept"); /* bad */
exit(1);
}
switch(fork())
{
/* try to handle connection */
case -1 : /* bad news. scream and die */ perror("fork");
close(s);
close(t);
exit(1);
case 0 : /* we're the child, do something */
close(s);
do_something(t);
exit(0);
default : /* we're the parent so look for */
close(t); /* another connection */
continue;
}
}
}
/* as children die we should get catch their returns or else we get * zombies, A Bad Thing. fireman() catches falling children. */
void fireman(void)
{
while (waitpid(-1, NULL, WNOHANG) > 0) ;
} /* this is the function that plays with the socket. it will be called * after getting a connection. */
void do_something(int s) { /* do your thing with the socket here : : */ }
拨号 (如何调用 socket)
     现在你应该知道如何建立 socket 来接受调用了。那么如何调用呢?和电话一样,你要先有个电话。用 socket() 函数来完成这件事情,就象建立侦听的 socket 一样。 在给 socket 地址后,你可以用 connect() 函数来连接侦听的 socket 了。下面是一段代码。
int call_socket(char *hostname, unsigned short portnum)
{
struct sockaddr_in sa;
struct hostent *hp;
int a, s;
if ((hp= gethostbyname(hostname)) == NULL)
{
/* do we know the host's */
errno= ECONNREFUSED; /* address? */
return(-1); /* no */ }
memset(&sa,0,sizeof(sa));
memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length); /* set address */
sa.sin_family= hp->h_addrtype;
sa.sin_port= htons((u_short)portnum);
if ((s= socket(hp->h_addrtype,SOCK_STREAM,0)) < 0) /* get socket */ return(-1);
if (connect(s,&sa,sizeof sa) < 0)
{
/* connect */
close(s);
return(-1);
}
return(s);
}

这个函数返回一个可以流过数据的 socket 。
谈话(如何通过 sockets 交谈) 好了,你在要传输数据的双方建立连接了,现在该传输数据了。read() 和 write() 函数来处理吧。除了在 socket 读写和文件读写中的一个区别外,和处理一般的文件一样。区别是你一般不能得到你所要的数目的数据。所以你要一直循环到你需要的数据的到来。
一个简单的例子:将一定的数据读到缓存。
int read_data(int s, /* connected socket */ char *buf, /* pointer to the buffer */ int n /* number of characters (bytes) we want */ )
{
int bcount; /* counts bytes read */
int br; /* bytes read this pass */
bcount= 0;
br= 0;
while (bcount < n)
{
/* loop until full buffer */
if ((br= read(s,buf,n-bcount)) > 0)
{
bcount += br; /* increment byte counter */
buf += br; /* move buffer ptr for next read */ }
else if (br < 0) /* signal an error to the caller */
return(-1); }
return(bcount); }
相同的函数也可以写数据,留给我们的读者吧。

挂起(结束)
     和你通过电话和某人交谈后一样,你要在 socket 间关闭连接。一般 close() 函数用来关闭每边的 socket 连接。如果一边的已经关闭,而另外一边却在向他写数据,则返回一个错误代码。

世界语(交流的语言很重要)
     现在你可以在机器间联络了,可是要小心你所说的话。许多机器有自己的方言,如 ASCII 和 EBCDIC。更常见的问题是字节顺序问题。除非你一直传输的都是文本,否则你一定要注意这个问题。幸运的是,人 们找出了解决的办法。在很久以前,人们争论哪种顺序更“正确”。现在必要时有相应的函数来转换。其中有 htons()、ntohs()、 htonl() 和 ntohl()。在传输一个整型数据前,先转换一下。
i= htonl(i);
write_data(s, &i, sizeof(i));
在读数据后,再变回来。 read_data(s, &i, sizeof(i)); i= ntohl(i); 如果你一直坚持这个习惯,你将比别人少出错的机会。

未来在你的掌握了(下一步?)
     就用我们刚才讨论的东西,你就可以写自己的通讯程序了。和对待所有的新生事物一样, 最好还是看看别人已经做 了些什么。这里有许多关于 BSD socket 的东西可以参考。 请注意,例子中没有错误检查,这在“真实”的程序中是很重要





















bzero
#include
void bzero(char *s, int n); 
  功能:置字节字符串s的前n个字节为零。The bzero() function copies n bytes, each with a value of zero, into string s.
s

    Points to a buffer that zeros are copied into.
n

    Is the number of zeros to be copied into the buffer.

  说明:bzero无返回值。
  举例:
      // bzero.c
     
      #include
      #include

      main()
      {
        struct
        {
          int a;
          char s[5];
          float f;
        } tt;
       
        char s[20];
       
        bzero(&tt,sizeof(tt));  // struct initialization to zero
        bzero(s,20);
       
        clrscr();
        printf("Initail Success");

        getchar();
        return 0;

2

1.什么是socket

所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。是网络编程的标准,最早出现在UNIX平台上,现在基本已经被所有平台支持。做网络开发可离不开。


2.如何开发一个Server-Client模型的程序

开发原理:

服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include           /* See NOTES */
       #include

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

DESCRIPTION
       socket()  creates  an endpoint for communication and returns a descrip-
       tor.


协议簇domain

       Name                Purpose                          Man page
       PF_UNIX, PF_LOCAL   Local communication              unix(7)
    PF_INET             IPv4 Internet protocols          ip(7)
       PF_INET6            IPv6 Internet protocols          ipv6(7)
       PF_IPX              IPX - Novell protocols
       PF_NETLINK          Kernel user interface device     netlink(7)
       PF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
       PF_AX25             Amateur radio AX.25 protocol
       PF_ATMPVC           Access to raw ATM PVCs
       PF_APPLETALK        Appletalk                        ddp(7)
       PF_PACKET           Low level packet interface       packet(7)


type类型(常用)

       SOCK_STREAM
              Provides sequenced,  reliable,  two-way,  connection-based  byte
              streams.  An out-of-band data transmission mechanism may be sup-
              ported.
      SOCK_DGRAM
              Supports datagrams (connectionless,  unreliable  messages  of  a
              fixed maximum length).

       SOCK_RAW
              Provides raw network protocol access.

      
协议protocol
The  protocol  specifies  a  particular  protocol  to  be used with the
       socket.  Normally only a single protocol exists to support a particular
       socket  type within a given protocol family, in which case protocol can
       be specified as 0.  However, it is possible  that  many  protocols  may
       exist,  in  which  case a particular protocol must be specified in this
       manner.  The protocol number to use is specific to  the  “communication
       domain” in which communication is to take place; see protocols(5).  See
       getprotoent(3) on how to map protocol name strings to protocol numbers.
一般设置为零
 
阅读(1508) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~