实验(No. 4)题目:简单的客户/服务器程序设计与实现
实验目的及要求:
1、熟悉 Microsoft Visual Studio 2008 编程环境。
2、了解 TCP 与 UDP 协议,以及它们之间的区别。
3、了解客户/服务器模型原理。
4、熟悉 Socket 编程原理,掌握简单的套接字编程。
实验设备:
硬件:PC 机(两台以上)、网卡、已经设定好的以太网环境
软件:Microsoft Visual Studio 2008
实验内容及步骤:
1、编写用 TCP 协议实现的 Client 端和 Server 端程序并调试通过。
程序分两部分:客户程序和服务器程序。
工作过程是: 服务器首先启动,它创建套接字之后等待客户的连接;客户启动后创建套接字,
然后和服务器建立连接;建立连接后,客户接收键盘输入,然后将数据发送到服务器,服务
器收到到数据后,将接收到的字符在屏幕上显示出来。或者服务器接收键盘输入,然后将数
据发送到客户机,客户机收到数据后,将接收到的字符在屏幕上显示出来。
程序流程如下:
服务器方
Socket()建立流式套接字,返回套接字号。
bind(),套接字 s 与本地地址相连。
listen(),通知 TCP,服务器准备好接收连接。
accept(),接受连接,等待客户端的连接...
连接建立,accept()返回,得到新的套接字,sc
recvt()/send(),在套接字 sc 上读/写数据,直到数
据交换完毕
closesocket(),关闭套接字 sc
closesocket(),关闭最初套接字 s,服务结束
客户方
Socket(),建立流失套接字,
返回套接字号
connect(),将套接字 s 与远
地主机连接
send()/recv(),在套接字上
读/写数据,直到数据交换
closesocket(),关闭套接字
结束 TCP 对话
2、编写用 UDP 协议实现的 Client 端和 Server 端程序并调试通过(做完第一个实验的基础上做该
实验)。
服务器方
客户方
Socket()建立流式套接字,返回套接字号。
Socket(),建立流失套接字,
返回套接字号
bind(),套接字 s 与本地地址相连。
将套接字与远地主机连接
recvt()/send(),在套接字上读/写数据,直到数据
交换完毕
send()/recv(),在套接字上
读/写数据,直到数据交换
closesocket(),关闭套接字
closesocket(),关闭套接字
结束 UDP 对话
3、编写用 TCP 协议实现 Client 端与 Server 端的一段对话程序。Server 端根据用户的输入来提示
Client 端下一步将要进行操作。
所用函数及结构体参考:
1、创建套接字——socket()
功能:使用前创建一个新的套接字
格式:SOCKET PASCAL FAR socket(int af,
参数:af:代表网络地址族,目前只有一种取值是有效的,即 AF_INET,代表 internet 地址族;
Type:代表网络协议类型,SOCK_DGRAM 代表 UDP 协议,SOCK_STREAM 代表 TCP
int procotol);
int type,
协议;
Protocol:指定网络地址族的特殊协议,目前无用,赋值 0 即可。
返回值为 SOCKET,若返回 INVALID_SOCKET 则失败。
2、指定本地地址——bind()
功能:将套接字地址与所创建的套接字号联系起来。
格式:int PASCAL FAR bind(SOCKET s,
参数:s: 是由 socket()调用返回的并且未作连接的套接字描述符(套接字号)。
其它:没有错误,bind()返回 0,否则 SOCKET_ERROR
地址结构说明:
const struct sockaddr FAR * name,
int namelen);
struct sockaddr_in
{
short sin_family;//AF_INET
u_short sin_port;//16 位端口号,网络字节顺序
struct in_addr sin_addr;//32 位 IP 地址,网络字节顺序
char sin_zero[8];//保留
}
3、建立套接字连接——connect()和 accept()
功能:共同完成连接工作
格式:int PASCAL FAR connect(SOCKET s,
SOCKET PASCAL FAR accept(SOCKET s,
参数:s: 是由 socket()调用返回的并且未作连接的套接字描述符(套接字号)。
const struct sockaddr FAR * name,
struct sockaddr FAR * name,
int namelen);
int FAR * addrlen);
4、监听连接——listen()
功能:用于面向连接服务器,表明它愿意接收连接。
格式:int PASCAL FAR listen(SOCKET s,
int backlog);
5、数据传输——send()与 recv()
功能:数据的发送与接收
格式:int PASCAL FAR send(SOCKET s,
int PASCAL FAR recv(SOCKET s,
参数:buf:指向存有传输数据的缓冲区的指针。
const char FAR* buf,
int len,
int flags);
const char FAR * buf,
int len,
int flags);
6、多路复用——select()
功能:用来检测一个或多个套接字状态。
格式:int PASCAL FAR select(int nfds,
fd_set FAR * exceptfds,
参数:readfds:指向要做读检测的指针
writefds:指向要做写检测的指针
exceptfds:指向要检测是否出错的指针
timeout:最大等待时间
fd_set FAR* readfds,
fd_set FAR* writefds,
const struct timeval FAR* timeout);
7、关闭套接字——closesocket()
功能:关闭套接字 s
格式:BOOL PASCAL FAR closesocket (SOCKET s);
8、WSADATA 类型和 LPWSADATA 类型
WSADATA 类型是一个结构,描述了 Socket 库的一些相关信息,其结构定义如下:
typedef struct WSAData {
WORD
WORD
char
char
unsigned short
unsigned short
wVersion;
wHighVersion;
szDescription[WSADESCRIPTION_LEN+1];
szSystemStatus[WSASYS_STATUS_LEN+1];
iMaxSockets;
iMaxUdpDg;
char FAR *
lpVendorInfo;
} WSADATA;
typedef WSADATA FAR *LPWSADATA;
值得注意的就是 wVersion 字段,存储了 Socket 的版本类型。LPWSADATA 是 WSADATA 的指针
类型。它们不用程序员手动填写,而是通过 Socket 的初始化函数 WSAStartup 读取出来。
9、sockaddr_in、in_addr 类型
sockaddr_in 定义了 socket 发送和接收数据包的地址。
定义:
struct sockaddr_in {
sin_family;
sin_addr;
short
u_short sin_port;
struct
char
in_addr
sin_zero[8];
};
其中 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;
首先阐述 in_addr 的含义,很显然它是一个存储 ip 地址的联合体,有三种表达方式:
(1)用四个字节来表示 IP 地址的四个数字;
(2)用两个双字节来表示 IP 地址;
(3)用一个长整型来表示 IP 地址。
给 in_addr 赋值的一种最简单方法是使用 inet_addr 函数,它可以把一个代表 IP 地址的字符串赋值
转换为 in_addr 类型,如
addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
本例子中由于是广播地址,所以没有使用这个函数。其反函数是 inet_ntoa,可以把一个 in_addr
类型转换为一个字符串。
sockaddr_in 的含义比 in_addr 的含义要广泛,其各个字段的含义和取值如下:
第一个字段 short
第二个字段 u_short sin_port,代表 IP 地址端口,由程序员指定;
第三个字段 struct in_addr sin_addr,代表 IP 地址;
sin_family,代表网络地址族,如前所述,只能取值 AF_INET;
第四个字段 char
sin_zero[8],是为了保证 sockaddr_in 与 SOCKADDR 类型的长度相等而填
充进来的字段。
Sever 端代码:
// server.cpp : 定义控制台应用程序的入口点。
#include
#include
#include
#include
#pragma comment(lib, "WS2_32")
SOCKET sock1,sock2;
int sin_size ;
struct sockaddr_in my_addr,their_addr;
char name[20];
//初始化函数 Tcp
void Init()
{
printf("\n\n\n
Server: TCP\n\n\n");
//建立套接字
const WORD wMinver=0x0101;
WSADATA wsadata;
if(0!=::WSAStartup(wMinver,&wsadata))
perror("Start socket error!");
if(INVALID_SOCKET==(sock1=::socket(AF_INET,SOCK_STREAM,0)))
perror("Create socket error!");
my_addr.sin_family=AF_INET;
my_addr.sin_addr.S_un.S_addr=INADDR_ANY;
my_addr.sin_port=htons(1000);
if(SOCKET_ERROR==::bind(sock1,(struct
sockaddr*)&my_addr,sizeof(my_addr)))
{
}
perror("Binding stream socket");
exit(1);
//开始侦听
if(SOCKET_ERROR==::listen(sock1,5))
{
perror("Listening stream socket");
exit(1);
}
//接受连接
printf("
Ready to serve client. Please
connect...\n\n\n");
sin_size = sizeof(struct sockaddr_in);
if((sock2=accept(sock1,(struct sockaddr *)&their_addr,&sin_size))==-1)
{
}
perror("Accepting stream socket");
exit(1);
printf("
connet:%s",inet_ntoa(their_addr.sin_addr));
Accepting a new
Server:
*
*
1.Send
Message
2.Receive Message
*
3.Exit
Enter your choice:");
}
//选择菜单
int menu()
{
Menu\n\n\n");
char *s=(char*)malloc(2*sizeof(char));
int c;
printf("\n\n\n
printf("
*********************************\n\n");
*\n");
*\n");
*\n\n");
printf("
printf("
printf("
printf("
*********************************\n");
do
{
printf("\n
gets(s);
if(s[0]=='\0'){
gets(s);
}
c=atoi(s);
}while(c<0||c>3);
free(s);
return c;
}
//消息发送函数
void Send()
{
char Msg[10240];
printf("\nPlease Input the message:");
gets(Msg);
Msg[10239]='\0';
::send(sock2,Msg,strlen(Msg),0);
}
//消息接收函数
void Receive()
{
int len;
char buf[10240];
for(int i=0;i<10240;i++){
buf[i]='\0';
}
if((len=::recv(sock2,buf,10240,0))==-1)
{
perror("Receving data error");
exit(1);
}
printf("The Received Message:%s\n",buf);
}
//主函数
void main()
{
Init();
for(;;)
{
switch(menu())
{
case 1:
Send();
break;
case 2:
Receive();
break;
case 3:
exit(0);
}
}
//::closesocket(sock2);
::closesocket(sock1);
}
::WSACleanup();
Server 端界面:
Client 端代码:
// client.cpp : 定义控制台应用程序的入口点。//
#include
#include