实验三socket编程实验指导.docx

上传人:b****7 文档编号:10261120 上传时间:2023-02-09 格式:DOCX 页数:17 大小:84.74KB
下载 相关 举报
实验三socket编程实验指导.docx_第1页
第1页 / 共17页
实验三socket编程实验指导.docx_第2页
第2页 / 共17页
实验三socket编程实验指导.docx_第3页
第3页 / 共17页
实验三socket编程实验指导.docx_第4页
第4页 / 共17页
实验三socket编程实验指导.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

实验三socket编程实验指导.docx

《实验三socket编程实验指导.docx》由会员分享,可在线阅读,更多相关《实验三socket编程实验指导.docx(17页珍藏版)》请在冰豆网上搜索。

实验三socket编程实验指导.docx

实验三socket编程实验指导

实验三socket套接字编程实验指导

一、Sockets编程基础知识

网络编程就是通过计算机网络与其他程序进行通信的程序,Socket编程是网络编程的主流工具。

SocketAPI是实现进程间通信的一种编程设施,也是一种为进程间提供底层抽象的机制。

尽管应用开发人员很少需要在该层编写代码,但是理解socketAPI还是非常重要的。

主要有两点原因:

第一,高层设施是构建于socketAPI之上的,它们是利用socketAPI提供的操作来实现。

第二,对于响应时间要求较高或运行于有限资源平台上的应用,甚至socketAPI是唯一可用的进程间通信设施。

socketAPI出现于20世纪80年代早期,作为BerkeleyUnix(BSD4.2)操作系统程序库来通过进程间通信功能。

现在主流操作系统都提供socketAPI。

在基于Unix系统中,如BSD、Linux系统,socketAPI是操作系统内核的一部分;在MS-DOS、WindowsOS、OS/2等操作系统中,socketAPI是以程序库形式提供的,如在Windows系统中,socketAPI被称为Winsock。

Socket接口规范可以适用多种通讯协议,主要是TCP/IP。

TCP/IP是计算机互联最常适用的网络通讯协议,TCP/IP的核心部分由网络操作系统的内核实现,应用程序通过编程接口来访问TCP/IP,应用程序通讯的方式有图3-1所示。

图3-1:

应用程序通信方式

TCP/IP使用一个网络地址和一个服务端口号来惟一地标识设备。

网络地址标识网络上的特定设备;端口号标识要连接到的该设备上的特定服务。

网络通讯的基本模式如下:

每一台通讯的主机都有一个本网络环境中惟一的IP地址,一台主机上往往有多个通讯程序存在,每个这样的程序都要占用一个通讯端口。

因此,一个IP地址,一个通讯端口,就能确定一个通讯程序的位置。

二、Socket通讯连接方式

Socket主要有三种通讯方式:

流式Socket、数据报Socket和原始Socket。

1.流式Socket(SOCK_STREAM)

流式套接字提供了基于Unix操作系统的流式I/O的数据传输模式,它仅仅支持通过可靠的、面向连接的通信,类似于电话系统服务,即每一次完整的数据传输都要经过建立连接、使用连接、终止连接的过程、在数据传输过程中,各数据分组不携带目的地址,而使用连接号(connectID)。

从本质上看,连接是一个管道,收发数据不但顺序一致,而且内容相同。

Socket编程中,双方进程可以单独创建流式Socket,再在socket之间形成连接。

一旦数据作为字符流被写入发送者进程,然后接收者通过socket来读取该数据。

面向连接通信能确保数据沿预先建立的连接安全有序地传输。

服务器使用两个Socket:

一个接收连接;一个发送/接收数据。

见图5-2。

图3-2:

流式Socket通信

2.数据报Socket(SOCK_DGRAM)

数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠、无差错。

类似于邮政系统服务。

每个分组都携带完整的目的地址,各分组独立传送。

无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。

UDP协议提供无连接的数据报服务。

它使用数据报协议UDP。

 

图3-3:

无连接数据包socket

3.原始Socket

原始套接字允许对底层协议如IP或ICMP直接访问,它功能强大但使用不便主要用于一些协议的开发。

三、基于C的面向连接的Socket编程模型

TCP协议是面向连接协议,它提供了一系列的数据纠错功能,可以保证在网络上传输的数据及时、无误的传给接收方。

因此面向连接协议的Socket编程模型应用最为广泛,基于连接协议的服务是设计客户端/服务器应用程序时的标准。

编程模型如图3-4所示。

模型中,服务器端的处理:

(1)使用socket系统调用,生成一个TCP协议模块与应用程序之间进行通信的套接字;使用bind系统调用指定端口号;

(2)使用bind系统调用指定端口号;

(3)使用listen系统调用,指定连接接收队列的长度,并等待来自客户端的连接请求。

前三步完成了启动服务器程序的工作。

一旦listen监听到有客户端的连接,就调用accept接收连接。

希望与服务器通信的进程称为客户,客户所运行的计算机环境称为客户端,有时两个概念混用。

客户端的处理:

(1)使用socket系统调用,打开TCP协议模块与应用程序之间的通信线路;

(2)使用connect系统调用,指定IP地址和端口号,和服务器相应的服务应用程序建立TCP协议的连接请求;

客户端和服务器程序在建立连接后,使用send和recv调用完成数据的发送和接收工作。

等待数据传送结束后,各自调用close关闭套接字。

使用TCP协议的Socket通信程序包括服务程序和客户程序。

图3-4:

基于C的面向连接的socket编程模型

基于C的Socket编程相关函数和数据类型

1.sockadd和sockaddr_in结构:

sockaddr结构

structsockaddr

{

unsignedshortsa_family;/*地址族,AF_xxx有IPV4与IPV6等*/

charsa_data[14];/*14字节的协议地址*/

};

sa_family一般为AF_INET,表示Internet协议族,如是AF_UNIX表示UNIX协议簇;sa_data中包含该socket的IP地址和端口号。

in_add结构,用来存储四字节的IP地址

structin_addr{

unsignedlongs_addr;

};

sockaddr_in结构

structsockaddr_in

{

shortintsin_family;/*地址族*/

unsignedshortintsin_port;/*端口号*/

structin_addsin_addr;/*IP地址*/

unsignedcharsin_zero[8];/*填充0以保持与structsockaddr同样大小*/

};

该结构中sin_zero使得sockaddr和sockaddr_in指针类型相互转换;sin_port和sin_addr必须是网络字节顺序,因为它们被封装在包的IP和UDP层,而sin_family不发送到网络上可以是本机字节顺序。

相关函数

1.socket()函数

该函数用于根据指定的地址族、数据类型和协议来分配一个套接字的描述字及其所用的资源。

Socket函数原型为:

intsocket(intdomain,inttype,intprotocol);

、参数domain指定地址描述,一般为AP_INET;

b、参数type指定socket类型:

SOCK_STREAM和SOCK_DGRAM;

c、参数protocol通常为0;

d、函数返回值为一个整型socket描述符,在bind函数中调用。

2.bind()函数

该函数用于将一个本地地址与一个套接字绑定在一起。

intbind(intsockfd,structsockadd*my_addr,intaddrlen);

、sockfd:

socket描述符,使用socket函数返回值,将该socket与本机上的一个端口相关联。

在设计服务器端程序是需要调用bind函数,以在该端口上监听服务请求;而客户端一般不需要调用bind函数,因为只需知道服务器IP地址,并不关心客户通过哪个端口与服务器建立连接,内核会自动选择一个未被占用的端口供客户端来使用。

、my_addr:

指向包含本机IP地址及端口号等信息的sockaddr类型的指针。

、addrlen:

sizeof(structsockaddr)的值。

、bind函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码。

3.connect()函数

与远程服务器建立一个TCP连接。

intconnect(intsockfd,structsockaddr*serv_addr,intaddrlen);

、sockfd:

目的服务器的socket描述符。

、serv_addr:

指向包含目的服务器的IP地址及端口号的指针。

、addrlen:

sizeof(structsockaddr)的值。

、connect函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码,进行服务器端程序设计时不需调用connect函数。

4.listen()函数

在服务器端程序中,当socket与某一端口绑定后,需要监听该端口,及时处理到达该端口上的服务请求。

intlisten(intsockfd,intbacklog);

、sockfd:

Socket系统调用返回的socket描述符。

、backlog:

指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待接收backlog限制了队列中等待服务的请求数目,系统缺省值为20。

、listen函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码。

5.accept()函数

当某个客户端试图与服务器监听的端口连接时,该连接请求将排队等待服务器用accept接收它并为其建立一个连接。

intaccept(intsockfd,structsockaddr*addr,int*addrlen);

、sockfd:

被监听的socket描述符。

、addr:

sockaddr类型的指针变量,用来存放提出连接请求服务的主机信息。

、accept函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码,如果没有错误,accept()函数返回一个新想socket描述符,供这个新连接来实用,而服务器可以继续在以前的socket上监听,同时可以在新的socket描述符上进行数据发送和数据接收(sent()和recv()操作)。

6.sent()和recv()函数

用于在面向连接(TCP)的socket上进行数据传输。

send()函数原型:

intsend(intsockfd,constvoid*msg,intlen,intflags);

、sockfd:

用于传输数据的socket描述符。

、msg:

是一个指向要发送数据的指针。

、len:

以字节为单位的数据的长度。

、flags:

一般情况下置为0。

、函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码,否则返回所发送数据的总数,该数字可能小于len中所规定的大小。

recv()函数原型:

intrecv(intsockfd,void*buf,intlen,unsignedintflags);

、sockfd:

是接收数据的socket描述符。

、buf:

是存放接收数据的缓冲区。

、len:

以字节为单位的缓冲区的长度。

、flags:

一般情况下置为0。

、函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码,无错则返回读入的字节数,如果连接被中止,返回0。

7.endto()和recvfrom()函数

这两个函数是利用数据报方式(UDP)进行数据传输。

在无连接的数据报socket方式下,由于本地socket并没有与远程机器建立连接,所以在发送数据时应指明目的地址。

sendto()原型:

intsendto(intsockfd,constvoid*msg,intlen,unsignedintflags,conststructsockaddr*to,inttolen);

、sockfd:

用于传输数据的socket描述符。

、msg:

是一个指向要发送数据的指针。

、len:

以字节为单位的数据的长度。

、flags:

一般情况下置为0。

、函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码,否则返回所发送数据的总数,该数字可能小于len中所规定的大小。

f、表示目的机器的IP地址和端口号。

g、tolen:

被赋值为sizeof(structsockaddr)。

recvfrom函数原型:

intrecv(intsockfd,void*buf,intlen,unsignedintflags,structsockaddr*from,intfromlen);

、sockfd:

是接收数据的socket描述符。

、buf:

是存放接收数据的缓冲区。

、len:

以字节为单位的缓冲区的长度。

、flags:

一般情况下置为0。

、函数返回值:

为-1表示遇到错误,并且errno中包含相应的错误码,无错则返回读入的字节数,如果连接被中止,返回0。

f、from:

保存源机器的IP地址和端口号。

g、fromlen:

常被赋值为sizeof(structsockaddr)。

当对于数据报socket调用了connect()函数时,也可以用send()和recv()进行数据传输,但该socket仍然是数据报socket,并利用传输层的UDP服务。

但是在发送或接收数据报时,内核会自动为它加上目的地址和源地址信息。

8.close()和shutdown()函数

当所有的数据操作结束后,可以调用close函数来释放该socket资源,从而停止在该socket上的任何数据操作。

也可以调用shutdown函数,允许只停止在某个方向上的数据传输,而另一个方向上的数据传输继续进行。

例如可以关闭某一个socket上的写操作uo允许继续在该socket上接收数据,直到读入所有数据。

但是,shutdown函数并不关闭套接字所占用的所有资源,除非调用close函数来释放。

看看两个函数原型:

close(intsockfd);

shutdown(intsockfd,inthow);

how参数的值和含义:

0:

不允许继续接收数据;

1:

不允许继续发送数据;

2:

不允许继续发送和接收数据。

shutdown在操作成功时返回0,错误时返回-1,并置errno值。

9.字节顺序转换函数

htons():

HosttoNetworkShort的缩写,该函数将主机的无符合短整型数字节顺序转换成网络字节顺序。

htonl():

HosttoNetworkLong的缩写,该函数将主机的无符合长整型数字节顺序转换成网络字节顺序。

ntohs():

NetworktoHostShort的缩写,该函数将无符号短整型数从网络字节顺序转换为主机字节顺序。

ntohl():

NetworktoHostlong的缩写,该函数将无符号长整型数从网络字节顺序转换为主机字节顺序。

四、服务器程序代码

#include"stdafx.h"

#include

#include

#include

#include

#pragmacomment(lib,"ws2_32.lib")

#defineMYPORT3490/*定义用户连接端口*/

#defineBACKLOG10/*多少等待连接控制*/

#defineSERVER_IP_ADDR"127.0.0.1"/*服务器的IP地址*/

int_tmain(intargc,_TCHAR*argv[])

{

SOCKETsock,msgsock;

intlength=0;

structsockaddr_inserver;

structsockaddrtcpaddr;

charbuf[1024]="";

intrval=0,len=0,err=0;

WORDwVersionRequested;

WSADATAwsaData;

/*指定socket版本,否则创建socket失败,即使创建socket返回值不为-1,但是bind时会失败*/

wVersionRequested=MAKEWORD(2,2);

err=WSAStartup(wVersionRequested,&wsaData);

if(err!

=0)

return-1;

/*建立套接字*/

sock=socket(AF_INET,SOCK_STREAM,0);

if(sock<0)

{

perror("openingstreamsocket");

exit

(1);

}

/*使用任意端口命名套接字*/

server.sin_family=AF_INET;

server.sin_port=htons(MYPORT);

server.sin_addr.s_addr=inet_addr(SERVER_IP_ADDR);

memset(server.sin_zero,0,sizeof(server.sin_zero));

//将服务器地址与socket绑定在一起

rval=bind(sock,(structsockaddr*)&server,sizeof(server));

if(rval<0)

{

perror("bindingstreamsocket");

exit

(1);

}

//找出指定的端口号并打印出来

length=sizeof(server);

if(getsockname(sock,(structsockaddr*)&server,&length)<0)

{

perror("gettingsocketname");

exit

(1);

}

printf("socketport#%d\n",ntohs(server.sin_port));

//开始接收连接,最大请求数为

listen(sock,5);

len=sizeof(structsockaddr);

do

{msgsock=accept(sock,(structsockaddr*)&tcpaddr,(int*)&len);

if(msgsock==-1)

perror("accept");

else

{memset(buf,0,sizeof(buf));

if((rval=recv(msgsock,buf,sizeof(buf),0)<0))

perror("readingstreammessage");

if(rval==0)

printf("-->%s\n",buf);

}

closesocket(msgsock);

}while(TRUE);

/*因为这个程序已经有了一个无限循环,所以套接字"sock"从来不显式关闭。

然而,当进程被杀死或正常终止时,所有套接字都将自动地被关闭。

*/

closesocket(msgsock);

return0;

}

五、客户端程序代码

#include"stdafx.h"

#include

#include

#include

#include

#pragmacomment(lib,"ws2_32.lib")

#definePORT3490/*客户机连接远程主机的端口*/

#defineMAXDATASIZE100/*每次可以接收的最大字节*/

int_tmain(intargc,_TCHAR*argv[])

{

WORDwVersionRequested;

WSADATAwsaData;

interr=0,rval=0;

SOCKETfd;

structsockaddr_inservaddr;

structhostent*hp;

charbuf[1024]="";

wVersionRequested=MAKEWORD(2,2);

err=WSAStartup(wVersionRequested,&wsaData);

if(err!

=0)

return-1;

if((fd=socket(AF_INET,SOCK_STREAM,0))<0)

{

printf("Cannotcreatesocket!

");

exit

(2);

}

servaddr.sin_family=AF_INET;

servaddr.sin_port=htons(PORT);

servaddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");

memset(servaddr.sin_zero,0,sizeof(servaddr.sin_zero));

//和服务器创建连接

rval=connect(fd,(sockaddr*)&servaddr,sizeof(servaddr));

if(rval<0)

{//创建连接失败

printf("Cannotcreateconnect!

");

exit(3);

}

else

{memset(buf,0,1024);

printf("Pleaseinputalinetoserver:

");

scanf("%s",&buf);

//向服务器发送信息

rval=send(fd,buf,strlen(buf)+1,0);

if(rval<0)

printf("Writeerror!

");

}

closesocket(fd);

exit(5);

return0;

}

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 其它课程

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1