420 likes | 777 Vues
Winsock 编程. 中科大软件学院. 实验三 socket 编程实现 SMTP/POP3 协议通信. winsock. Windows Sockets 规范本意在于提供给应用程序开发者一套简单的 API ,并让各家网络软件供应商共同遵守。 Windows Sockets 规范定义并记录了如何使用 API 与 Internet 协议族( IPS ,通常我们指的是 TCP/IP )连接,尤其要指出的是所有的 Windows Sockets 实现都支持流套接口和数据报套接口 . 应用程序调用 Windows Sockets 的 API 实现相互之间的通讯。. Socket ?.
E N D
Winsock编程 中科大软件学院 实验三 socket编程实现SMTP/POP3协议通信
winsock Windows Sockets规范本意在于提供给应用程序开发者一套简单的API,并让各家网络软件供应商共同遵守。 Windows Sockets规范定义并记录了如何使用API与Internet协议族(IPS,通常我们指的是TCP/IP)连接,尤其要指出的是所有的Windows Sockets实现都支持流套接口和数据报套接口.应用程序调用Windows Sockets的API实现相互之间的通讯。
Socket? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket将复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
TCP 服务器端程序示例(一) #include <stdio.h> //引入winsock库 #include <winsock2.h> #pragma comment(lib,"WS2_32")
#pragma comment(lib,"WS2_32") #pragma是标准C/C++的内容,用于向编译器传递特定参数。具体参数则与编译器有关 comment(lib,“WS2_32”)特定于VC的参数,并不通用 #pragma comment(lib,“WS2_32”)实现链接WS2_32.lib
加载、释放winsock库(WS2_32.DLL) WORD wVersion = MAKEWORD(2, 0); WSADATA wsData; if (WSAStartup(wVersion, &wsData)!=0) { printf("初始化失败!\n"); }
WSAStartup()函数 int WSAStartup( WORD wVersionRequested, LPWSDATA lpWSAData ) wVersionRequested:指定要加载的winsock库的版本,高字节为次版本号,低字节为主版本号 一个指向WSADATA结构的指针,用来返回DLL库的详细信息 wVersionRequested的值可使用宏MAKEWORD(x,y)来建立,x为高字节,y为低字节
结构体WSADATA typedef struct WSAData{ WORD wVersion; //库文件建议应用程序使用的版本 WORD wHighVersion; //库文件支持的最高版本 //库描述字符串 char szDescription[WSADESCRIPTION_LEN+1]; //系统状态字符串 char szSystemStatus[WSASYS_STATUS_LEN+1]; //同时支持的最大套接字数量 unsigned short iMaxSockets; //以下两个参数在2.0版中已废弃 unsigned short iMaxUdpDg; char FAR* lpVendorInfo; } WSDATA,FAR * LPWSADATA;
TCP 服务器端程序示例(二) //创建套接字 SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0); if (sockSrv== INVALID_SOCKET) { printf("Failed socket()\n"); return 1; }
Socket()函数 SOCKET socket( //用来指定套接字使用的地址格式,通常使用AF_INET int af, /* 地址家族, AF_xxx */ //指定套接字的类型 int type, //配合type参数使用,指定使用的协议类型 int protocol )
套接字类型type SOCK_STREAM(RFC-793 ) 流套接字,使用tcp提供的有连接的可靠的传输 SOCK_DGRAM(RFC-768) 数据报套接字,使用udp提供无连接的不可靠的传输 SOCK_RAW 原始套接字,socket不使用特定的协议区封装它,而由程序自行处理数据报及协议首部
协议类型protocol 配合type使用,值可以是IPPROTO_TCP等 当type指定为SOCK_STREAM或SOCK_DGRAM时,因为系统已明确使用tcp和udp来工作,protocol可指定为0
填充socket addr_in结构 //填充socket addr_in结构 sockaddr_in addServer,addrClient; addServer.sin_family=AF_INET; addServer.sin_addr.S_un.S_addr=inet_addr(Local_IP); addServer.sin_port=htons(8000);
sockaddr结构体 struct sockaddr{ u_short sa_family; char sa_data[14]; } Winsock为了兼容多个协议,所采用的通用的寻址方式
sockaddr_in结构体 struct sockaddr_in { //地址族(指定地址格式) ,设为AF_INET short sa_family; u_short sin_port; //端口号 struct in_addr sin_addr; //IP地址 char sin_zero[8]; //空子节,设为空 } 用于TCP/IP的sockaddr
sin_port 端口号通常分为三个范围 0 ~ 1023:由IANA(Internet Assigned Numbers Authority)管理,保留为公共的服务使用 1024 ~ 49151:普通用户注册的端口号 49152 ~ 65535:动态或私有的端口号
sin_addr域:struct in_addr struct in_addr{ union{ struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un; };
struct in_addr说明 采用联合体方式提供了32位IP地址的不同表示方法 unsigned long inet_addr(const char* cp); 将由小数点分隔的十进制IP地址字符串转化成由32位二进制数表示的IP地址 char* inet_ntoa(struct in_addr in); 将一个网络字节顺序的32位IP地址转化成字符串
TCP 服务器端程序示例(三) //绑定套接字到一个本地地址 if(bind(sockSrv,(SOCKADDR*)&addServer,sizeof(SOCKADDR))!=0) { printf("端口连接失败!\n"); }
bind()函数 int bind( SOCKET s; //套接字句柄 //要关联的本地地址 const struct sockaddr* name, int namelen //地址的长度 )
bind()函数 错误提示: EACCES:地址受到保护,用户非超级用户。 EADDRINUSE:指定的地址已经在使用。 EBADF:sockfd参数为非法的文件描述符。 EINVAL:socket已经和地址绑定。
TCP 服务器端程序示例(四) //进入监听模式 if (listen(sockSrv,5) == SOCKET_ERROR) { printf("Failed listen()\n"); }
Listen()函数 int listen( //套接字句柄 SOCKET s, //监听队列中允许保持的尚未处理的最大连接数 int backlog )
TCP 服务器端程序示例(五) //接受新连接 addrClient.sin_family=AF_INET; addrClient.sin_addr.S_un.S_addr=INADDR_ANY; addrClient.sin_port=htons(8000); printf("等待客户端连接……\n"); SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClient,&len);
accept()函数 SOCKET accept( //套接字句柄 SOCKET s, //一个指向sockaddr_in结构的指针,用于获取对方地址 struct sockaddr* addr, //一个指向地址长度的指针 int * addrlen )
Recv()函数 int recv( SOCKET s, char FAR* buf, int len, int flags); s:一个标识已连接套接口的描述字。 buf:用于接收数据的缓冲区。 len:缓冲区长度。 flags:指定调用方式。 如:recv(sockCli,recvBuf,len,0);
TCP 服务器端程序示例(六) //向客户端发送数据 send(sockConn,sendBuf,strlen(sendBuf),0); 附:char sendBuf [] = "TCP Server Demo!\r\n";
send()函数 int send( //套接字句柄 SOCKET s, //存储发送数据的缓冲区地址 const char FAR* buf, //缓冲区长度 int len, //指定了调用方式,通常设为0 int flags )
TCP 客户端程序示例(一) if(connect(sockCli,(SOCKADDR*)&addClient,sizeof(SOCKADDR))!=0) { printf("连接服务器失败!\n"); }
connect()函数 int connect( //套接字句柄 SOCKET s, //指向sockaddr_in结构的指针,包含了要连接的服务器的地址信息 const struct sockaddr FAR * name, //sockaddr_in结构的长度 int namelen )
read和write read(int fd,void *buf,size_t nbyte) read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数, 如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误. 如果错误为EINTR说明读是由中断引起的, 如果是ECONNREST表示网络连接出了问题. 和上面一样,我们也写一个自己的读函数. write(int fd,const void *buf,size_t nbytes) write函数将buf中的nbytes字节内容写入文件描述符fd. 成功时返回写的字节数.失败时返回-1. 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有俩种可能. 1)write的返回值大于0,表示写了部分或者是全部的数据. 2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理. 如果错误为EINTR表示在写的时候出现了中断错误. 如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接). 为了处理以上的情况,我们自己编写一个写函数来处理这几种情况.
recv和send 和read和write差不多.不过它们提供 了第四个参数来控制读写操作. int recv(int sockfd,void *buf,int len,int flags) int send(int sockfd,void *buf,int len,int flags) 前面的三个参数和read,write一样,第四个参数可以是0或者是以下的组合 ________________________________________ | MSG_DONTROUTE | 不查找路由表 | | MSG_OOB | 接受或者发送带外数据 | | MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 | | MSG_WAITALL | 等待所有数据 | |--------------------------------------------------------------| MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程序里面. MSG_OOB:表示可以接收和发送带外的数据. MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容.这样下次读的时候,仍然是一样的内容.一般在有多个进程读写数据时可以使用这个标志. MSG_WAITALL是recv函数的使用标志,表示等到所有的信息到达时才返回.使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误. 1)当读到了指定的字节时,函数正常返回.返回值等于len 2)当读到了文件的结尾时,函数正常返回.返回值小于len 3)当操作发生错误时,返回-1,且设置错误为相应的错误号(errno) 如果flags为0,则和read,write一样的操作
实验报告内容: 功能:通过Socket通信,实现POP3及SMTP的邮件通信服务。 代码实现提示,如: send(sockCli,"HELO\r\n",strlen("HELO\r\n"),0); 提交程序(伪)代码 要求:禁止相互拷贝 独立完成