黄荷凤:Socket网络编程指导

来源:百度文库 编辑:九乡新闻网 时间:2024/05/02 06:14:00

Socket网络编程指导

2011-3-19

1/37

guoxiaol@mail.ustc.edu.cn

什么是Socket?

2011-3-19

2/37

BSD Socket(伯克立套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。

 

Socket是独立于具体协议的网络编程接口。在ISO模型中,主要位于会话层和传输层。

Socket的类型

2011-3-19

3/37

流式套接字(SOCK_STREAM)
    提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。

 

数据报式套接字(SOCK_DGRAM)
    提供了一个无连接服务。数据包以独立包形式被发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序无序。

 

原始式套接字(SOCK_RAW)
    该接口允许对较低层次协议,如IP、ICMP直接访问。

2011-3-19

4/37

Socket所在层次示意图

Application program

Stream

Socket

Interface

TCP

UDP

Datagram

Socket

Interface

Raw

Socket

Interface

IP

Physical and data link layers

基本套接字调用

2011-3-19

5/37

创建套接字    socket();
绑定本机端口       bind();
建立连接              connect();

       接受连接              accept();
监听端口              listen();
数据传输              send(), recv() 等;
关闭套接字    close();

Socket相关的数据结构

2011-3-19

6/37

struct sockaddr_in

   {
   short int   sin_family;      /* 通信类型 */
   unsigned short int   sin_port;  /* 端口号,网络字节顺序*/
   struct in_addr  sin_addr;  /* Internet 地址,网络字节顺序*/
   unsigned char sin_zero[8];   /*没用*/

    };

 

struct in_addr

    {

             in_addr_t    s_addr;  /* 存储32bit 的IP地址*/

    }

网络字节顺序和主机字节顺序

2011-3-19

7/37

Big-Endian Byte Order:字节的高位在内存中放在存储单元的起始位置

00001010

00010111

00001110

00000110

00001010

00010111

00001110

00000110

Memory

Little-Endian Byte Order : 与Big-Endian相反

A

A+1

A+2

A+3

2011-3-19

8/40

Host byte order( Little-Endian )

16-bit

32-bit

Network byte order(Big-Endian)

16-bit

32-bit

htons()

ntohs()

htonl()

ntohl()

网络字节顺序和主机字节顺序的转换

IP地址的转换

2011-3-19

9/37

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

   从点状十进制到32位2进制的转换,如“202.38.64.185” 到

   11001010,00100110,01000000,10111001

char  *inet_ntoa(struct in_addr inadd);

   与inet_aton()的功能相反

相关的内存操作函数

2011-3-19

10/37

void *memset(void *buffer, int c, int count);

    把buffer所指内存区域的前count个字节设置成字符c。

void *memcpy(void *dest, void *src, unsigned int                                    count);

   由src所指内存区域复制count个字节到dest所指内存区域。

Void bzero(void *s, int n );

    置字节字符串s的前n个字节为零。

域名和IP地址的转换

2011-3-19

11/37

struct hostent *gethostbyname(const char *name);

struct hostent

{
 char    *h_name; /* 主机的官方域名 */
 char  **h_aliases; /* 一个以NULL结尾的主机别名数组 */
 int  h_addrtype; /* 返回的地址类型,在Internet环境下为AF-                                 INET */
 int  h_length; /* 地址的字节长度 */
 char  **h_addr_list; /* 一个以0结尾的数组,包含该主机的所有地                     址*/
};
#define h_addr h_addr_list[0]  /*在h-addr-list中的第一个地址*/

建立Socket

2011-3-19

12/37

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

参数说明:

       domain:通信使用的协议族,即网络的类型,对于             TCP/IP来说,是AF_INET

    type: SOCK_STREAM / SOCK_DGRAM

    protocol: 通常为0

 

 返回整形的socket描述符,如果出错,返回-1

Socket的配置

2011-3-19

13/37

Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket()函数时,将建立一个Socket,为一个Socket数据结构分配存储空间。

两个网络程序之间的一个网络连接包括五种信息:通信协议、本地主机地址和端口、远端主机地址和端口。

在使用socket进行网络传输以前,必须配置该socket。       面向连接的socket客户端调用connect()函数在socket数据结构中保存本地和远端信息。

              无连接socket的客户端和服务端以及面向连接socket的服务端通过调用bind()函数来配置本地信息。

绑定Socket

2011-3-19

14/37

int bind(int sockfd,struct sockaddr_in *my_addr, int                   addrlen);

    sockfd是socket()返回的socket描述符;

    my_addr是指向包含本机IP地址及端口号等信息的                      sockaddr类型的指针;                                          addrlen一般被设置为sizeof(struct sockaddr_in)


成功被调用时返回0;出现错误时返回"-1" 

绑定前sockaddr_in的初始化

2011-3-19

15/37

my_addr.sin_family = AF_INET;     //选择网络类型为TCP/IP

my_addr.sin_addr.s_addr = inet_addr(“210.45.64.222");

my_addr.sin_port = htons( 8888 );  //选择端口8888

addr_len = sizeof(struct sockaddr_in);               

memset(&my_addr.sin_zero, ‘\0’, sizeof(my_addr.sin_zero));  

建立连接(客户端)

2011-3-19

16/37

面向连接的客户程序使用connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:
int connect( int sockfd, struct sockaddr_in                                 *serv_addr,int addrlen);

serv_addr是包含远端主机IP地址和端口号的指针;addrlen是远端地址结构的长度

成功则返回0,出现错误时返回-1

建立连接(服务器端)

2011-3-19

17/37

服务器监听端口:listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
int listen(int sockfd, int backlog);

backlog:请求连接队列的最大长度

成功返回0,出错返回-1

建立连接(服务器端)

2011-3-19

18/37

accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用accept函数,然后睡眠并等待客户的连接请求。
int accept(int sockfd, sockaddr_in *addr, int                   *addrlen);

addr是指向sockaddr_in变量的指针,该变量存放提出连接请求服务的主机的信息

返回新的socket描述符,和请求连接进程的地址联系起来在新的socket描述符上进行数据传输操作。原来的socket继续listen

数据传输(1)

2011-3-19

19/37

send()和recv()这两个函数用于面向连接的socket上进行数据传输。
send()函数原型为:
int send(int sockfd, const void *msg, int len, int                      flags);     
    sockfd是用来传输数据的socket描述符;msg是指向要发送数据的指针;len是以字节为单位的数据长度;flags一般置为0

send() 返回实际发送的字节数,可能会少于希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当返回值与len不匹配时,应该进行处理。

数据传输(2)

2011-3-19

20/37

recv()函数原型为:
int recv(int sockfd, void *buf, int len, unsigned int            flags);

buf 是存放接收数据的缓冲区;len是缓冲区的长度。flags也被置为0。

recv()返回实际接收的字节数,当出现错误时,返回-1

数据传输(3)

2011-3-19

21/37

sendto()和recvfrom()用于在无连接的数据报socket方

    式下进行数据传输。由于本地socket没有与远端机器建立连接,所以在发送数据时要指明目的地址。
     

sendto()函数原型为:

    int sendto(int sockfd, const void *buf,int buflen,                 unsigned  int flags, const struct                             sockaddr_in *to,  int tolen); 

数据传输(4)

2011-3-19

22/37

recvfrom()函数原型为:
int recvfrom(int sockfd,void *buf,int buflen,                          unsigned int flags,struct sockaddr_in                        *from,int  *fromlen);

 

recvfrom()函数返回接收到的字节数,当出错时返回-1

结束传输

2011-3-19

23/37

close()函数用于释放socket,停止在该socket上的任何数据操作:     close(sockfd);
    

也可以调用shutdown() 来关闭该socket

       该函数允许只停止某个方向上的数据传输,而一个方向上的数据传输继续进行。
    int shutdown(int sockfd,int how);
   参数 how允许为shutdown操作选择以下几种方式:
    0-------不允许继续接收数据
    1-------不允许继续发送数据
    2-------不允许继续发送和接收数据,
    shutdown在操作成功时返回0,出错时返回-1。

C/S结构

2011-3-19

24/37

服务器端要先启动,提供相应服务:

  1:打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。

  2:等待客户请求到达该端口。

  3:接收到服务请求,处理该请求并发送应答信号。

  4:返回第二步,等待另一客户请求

  5:关闭服务器。

客户端:

  1、打开一通信通道,并连接到服务器所在主机的特定端口。

  2、向服务器发送服务请求报文,等待并接收应答;继续提出请求……

  3、请求结束后关闭通信通道并终止。

流程图

2011-3-19

25/37

TCP服务器端

(循环服务器)

TCP客户端

socket( )

bind( )

listen( )

accept( )

socket( )

send( )

connect( )

recv( )

recv( )

send( )

close( )

close( )

UDP服务器端

UDP客户端

socket( )

bind( )

listen( )

recvfrom( )

sendto( )

socket( )

bind( )

close( )

close( )

简单的例子

2011-3-19

26/37

int sockfd, newsockfd,addr_len, sendnum;

struct sockaddr_in my_addr, their_addr;

char * msg = “welcome”;

sockfd = socket( AF_INET, SOCK_STREAM, 0 );  //建立socket

 

my_addr.sin_family = AF_INET;                           //选择网络类型为TCP/IP

my_addr.sin_addr.s_addr = inet_addr(“210.45.64.222");

my_addr.sin_port = htons( 8888 );                        //选择端口8888

addr_len = sizeof( struct sockaddr_in);               

memset(&my_addr.sin_zero, ‘\0’, sizeof(my_addr.sin_zero));  

 

bind(sockfd, (struct sockaddr *)&my_addr, addr_len);   //绑定socket

 

listen(sockfd,10);         //监听,等待连接,等待连接队列最大长度为10

简单的例子(续)

2011-3-19

27/37

While( 1 )

{

       newsockfd = accept( sockfd, (struct sockaddr *)&my_addr, addr_len);

      sendnum = send(newsockfd, msg, strlen(msg)+1, 0);

      ……

      close(newsockfd);

 }

 

 close(sockfd);

阻塞与非阻塞(1)

2011-3-19

28/37

阻塞函数:指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。

    eg: recv( ) ,当socket工作在阻塞模式的时候,如果没有数据的情况下调用该函数,则当前线程会被挂起,直到有数据为止。

 

非阻塞函数:指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。

使用非阻塞I/O的方式:select()

例子:

while(1){//执行循环 一边输出一边也不要忘了输入

              FD_ZERO(&wt_set); FD_ZERO(&rd_set);

              FD_CLR(s,&wt_set); FD_CLR(s,&rd_set);

              FD_SET(s,&wt_set); FD_SET(s,&rd_set);

          timeout.tv_sec = 0;

              timeout.tv_usec =500000;

                 z=select(s+1,&rd_set,&wt_set,NULL,&timeout);

            if(FD_ISSET(s,&rd_set)){//有数据可读

                          z=recv(s,&recvBuff,sizeof recvBuff-1,0);

2011-3-19

29/37

阻塞与非阻塞(2)

2011-3-19

30/37

在Berkeley socket函数部分中,不涉及网络I/O、本地端工作的函数是非阻塞函数

在Berkeley socket函数部分中,网络I/O的函数是可阻塞函数,也就是它们可以阻塞执行,也可以不阻塞执行。这些函数都使用了一个socket,如果它们使用的socket是阻塞的,则这些函数是阻塞函数;如果它们使用的socket是非阻塞的,则这些函数是非阻塞函数。

并发服务器

2011-3-19

31/37

TCP服务器端(并发服务器)

socket( )

bind( )

listen( )

accept( )

send( )

recv( )

close( )

fork( ) //派生新进程

close( )

主进程在accept之后派生新进程,然后主进程继续listen,处理新的连接请求

     新进程自行和客户端通信,新进程和主进程抢占CPU

WinSock API

2011-3-19

32/37

WinSock是一个基于Socket模型的API,在Microsoft Windows操作系统类中使用。

它在Berkeley接口函数的基础之上,还增加了基于消息驱动机制的Windows扩展函数。

Winscok1.1只支持TCP/IP网络,WinSock2.0增加了对更多协议的支持。

Windows下的Socket编程(1)

2011-3-19

33/37

和linux下基本相同,需要包含winsock2.h

需要使用Ws_32.lib,可以用以下语句通告程序编译时调用该库:

    #pragma comment(lib,"Ws2_32.lib") ;

WinSock以DLL的形式提供,在调用任何WinSock API之前,必须调用函数WSAStartup()进行初始化,最后,调用函数WSACleanUp()作清理工作。

Windows下的Socket编程(2)

2011-3-19

34/37

WSADATA wsd;

//设置WINSOCK的版本

WORD wVersionRequested=MAKEWORD(2,2);

WSAStartup(wVersionRequested,&wsd) ; //初始化

 。。。。。。。。。    

 WSACleanUp();

Windows下的Socket编程(3)

2011-3-19

35/37

MFC提供了两个类CAsyncSocket和CSocket来封装WinSock API,提供了更简单的网络编程接口。

CAsyncSocket在较低层次上封装了WinSock API,缺省情况下,使用该类创建的socket是非阻塞的socket,所有操作都会立即返回,如果没有得到结果,返回WSAEWOULDBLOCK,表示是一个阻塞操作。

Windows下的Socket编程(4)

2011-3-19

36/37

CSocket是CAsyncSocket的派生类,缺省情况下使用该类创建的socket是非阻塞的socket,但是CSocket的网络I/O是阻塞的,它在完成任务之后才返回。

CSocket的阻塞不是建立在“阻塞”socket的基础上,而是在“非阻塞”socket上实现的阻塞操作

网络编程作业要求

2011-3-19

37/37

 

不分组,每人独立完成。

基于C/S或P2P结构,使用UDP或TCP协议皆可。

最好使用基本SOCKET API,不反对使用CAsyncSocket和

   CSocket类,但不准使用和传输相关的控件。

期末提交设计文档,源码,及可执行文件。

提交时间为12月份,具体提交日期及提交方式待定。

可选题目I

BBS发帖程序

通过term方式(202.38.64.3:23)或者通过web方式

完成在test版发一贴的功能(多发会被永久封账号)

Bbs账号和密码使用命令行参数或其他方式设置,不要直接写在程序里

对于term方式下的程序,要求能监视程序运行过程(也就是说在程序运行的时候要把服务器的输出打印到屏幕)

Referrence:

http协议:http://en.wikipedia.org/wiki/HTTP

2011-3-19

38/37

可选题目II

完成一个HTTP服务器

使用HTTP 1.1协议

支持最大至少10个并发连接(fork创建子进程)

要求服务器程序运行以后,能在浏览器中访问文件,正常显示

Web server的根目录使用命令行参数或者其他方式制定,不要写在程序代码里

文件不存在时返回浏览器404错误

2011-3-19

39/37

2011-3-19

40/37

谢谢