黄荷凤:Socket网络编程指导
来源:百度文库 编辑:九乡新闻网 时间:2024/05/02 06:14:00
Socket网络编程指导
1/37
guoxiaol@mail.ustc.edu.cn
什么是Socket?
2/37
BSD Socket(伯克立套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。
Socket是独立于具体协议的网络编程接口。在ISO模型中,主要位于会话层和传输层。
Socket的类型
3/37
流式套接字(SOCK_STREAM)
提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。
数据报式套接字(SOCK_DGRAM)
提供了一个无连接服务。数据包以独立包形式被发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序无序。
原始式套接字(SOCK_RAW)
该接口允许对较低层次协议,如IP、ICMP直接访问。
4/37
Socket所在层次示意图
Application program
Stream
Socket
Interface
TCP
UDP
Datagram
Socket
Interface
Raw
Socket
Interface
IP
Physical and data link layers
基本套接字调用
5/37
创建套接字 socket();
绑定本机端口 bind();
建立连接 connect();
接受连接 accept();
监听端口 listen();
数据传输 send(), recv() 等;
关闭套接字 close();
Socket相关的数据结构
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地址*/
}
网络字节顺序和主机字节顺序
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
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地址的转换
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()的功能相反
相关的内存操作函数
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地址的转换
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
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的配置
13/37
Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket()函数时,将建立一个Socket,为一个Socket数据结构分配存储空间。
两个网络程序之间的一个网络连接包括五种信息:通信协议、本地主机地址和端口、远端主机地址和端口。
在使用socket进行网络传输以前,必须配置该socket。 面向连接的socket客户端调用connect()函数在socket数据结构中保存本地和远端信息。
无连接socket的客户端和服务端以及面向连接socket的服务端通过调用bind()函数来配置本地信息。
绑定Socket
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的初始化
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));
建立连接(客户端)
16/37
面向连接的客户程序使用connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:
int connect( int sockfd, struct sockaddr_in *serv_addr,int addrlen);
serv_addr是包含远端主机IP地址和端口号的指针;addrlen是远端地址结构的长度
成功则返回0,出现错误时返回-1
建立连接(服务器端)
17/37
服务器监听端口:listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
int listen(int sockfd, int backlog);
backlog:请求连接队列的最大长度
成功返回0,出错返回-1
建立连接(服务器端)
18/37
accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用accept函数,然后睡眠并等待客户的连接请求。
int accept(int sockfd, sockaddr_in *addr, int *addrlen);
addr是指向sockaddr_in变量的指针,该变量存放提出连接请求服务的主机的信息
返回新的socket描述符,和请求连接进程的地址联系起来在新的socket描述符上进行数据传输操作。原来的socket继续listen
数据传输(1)
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)
20/37
recv()函数原型为:
int recv(int sockfd, void *buf, int len, unsigned int flags);
buf 是存放接收数据的缓冲区;len是缓冲区的长度。flags也被置为0。
recv()返回实际接收的字节数,当出现错误时,返回-1
数据传输(3)
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)
22/37
recvfrom()函数原型为:
int recvfrom(int sockfd,void *buf,int buflen, unsigned int flags,struct sockaddr_in *from,int *fromlen);
recvfrom()函数返回接收到的字节数,当出错时返回-1
结束传输
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结构
24/37
服务器端要先启动,提供相应服务:
1:打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。
2:等待客户请求到达该端口。
3:接收到服务请求,处理该请求并发送应答信号。
4:返回第二步,等待另一客户请求
5:关闭服务器。
客户端:
1、打开一通信通道,并连接到服务器所在主机的特定端口。
2、向服务器发送服务请求报文,等待并接收应答;继续提出请求……
3、请求结束后关闭通信通道并终止。
流程图
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( )
简单的例子
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
简单的例子(续)
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)
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);
29/37
阻塞与非阻塞(2)
30/37
在Berkeley socket函数部分中,不涉及网络I/O、本地端工作的函数是非阻塞函数
在Berkeley socket函数部分中,网络I/O的函数是可阻塞函数,也就是它们可以阻塞执行,也可以不阻塞执行。这些函数都使用了一个socket,如果它们使用的socket是阻塞的,则这些函数是阻塞函数;如果它们使用的socket是非阻塞的,则这些函数是非阻塞函数。
并发服务器
31/37
TCP服务器端(并发服务器)
socket( )
bind( )
listen( )
accept( )
send( )
recv( )
close( )
fork( ) //派生新进程
close( )
主进程在accept之后派生新进程,然后主进程继续listen,处理新的连接请求
新进程自行和客户端通信,新进程和主进程抢占CPU
WinSock API
32/37
WinSock是一个基于Socket模型的API,在Microsoft Windows操作系统类中使用。
它在Berkeley接口函数的基础之上,还增加了基于消息驱动机制的Windows扩展函数。
Winscok1.1只支持TCP/IP网络,WinSock2.0增加了对更多协议的支持。
Windows下的Socket编程(1)
33/37
和linux下基本相同,需要包含winsock2.h
需要使用Ws_32.lib,可以用以下语句通告程序编译时调用该库:
#pragma comment(lib,"Ws2_32.lib") ;
WinSock以DLL的形式提供,在调用任何WinSock API之前,必须调用函数WSAStartup()进行初始化,最后,调用函数WSACleanUp()作清理工作。
Windows下的Socket编程(2)
34/37
WSADATA wsd;
//设置WINSOCK的版本
WORD wVersionRequested=MAKEWORD(2,2);
WSAStartup(wVersionRequested,&wsd) ; //初始化
。。。。。。。。。
WSACleanUp();
Windows下的Socket编程(3)
35/37
MFC提供了两个类CAsyncSocket和CSocket来封装WinSock API,提供了更简单的网络编程接口。
CAsyncSocket在较低层次上封装了WinSock API,缺省情况下,使用该类创建的socket是非阻塞的socket,所有操作都会立即返回,如果没有得到结果,返回WSAEWOULDBLOCK,表示是一个阻塞操作。
Windows下的Socket编程(4)
36/37
CSocket是CAsyncSocket的派生类,缺省情况下使用该类创建的socket是非阻塞的socket,但是CSocket的网络I/O是阻塞的,它在完成任务之后才返回。
CSocket的阻塞不是建立在“阻塞”socket的基础上,而是在“非阻塞”socket上实现的阻塞操作
网络编程作业要求
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
38/37
可选题目II
完成一个HTTP服务器
使用HTTP 1.1协议
支持最大至少10个并发连接(fork创建子进程)
要求服务器程序运行以后,能在浏览器中访问文件,正常显示
Web server的根目录使用命令行参数或者其他方式制定,不要写在程序代码里
文件不存在时返回浏览器404错误
39/37
40/37
谢谢