1、实验七:网络编程
l 实验目的:
学会Linux的socket套接字网络编程,熟悉使用TCP传输协议的网络编程流程
l 实验要求:
编写使用TCP协议的服务器程序和客户端程序,客户端向服务器发送字符串,服务器打印收到的字符串
l 实验器材:
软件:安装了Linux的vmware虚拟机
硬件:PC机一台
l 实验步骤:
1、编写服务器端代码tcp_server.c
#include
2、include
3、fd描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)// AF_INET:IPV4;SOCK_STREAM:TCP { fprintf(stderr,"Socket error:%s\n\a",strerror(errno)); exit(1); } /* 2、服务器端填充 sockaddr结构 */ bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0 server_addr.sin_family=AF_INET;
4、 // Internet server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // (将本机器上的long数据转化为网络上的long数据)服务器程序能运行在任何ip的主机上 //server_addr.sin_addr.s_addr=inet_addr("192.168.1.1"); //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的shor
5、t数据)端口号 /* 3、绑定地址结构体 */ if( bind(sockfd, &server_addr,sizeof(server_addr))<0) { printf(“bind error!”); exit(-1); } /* 4、设置监听允许连接的最大客户端数 */ if( listen( sockfd, 0)<0) { printf(“listen error!”); exit(-1); } while(1) { /* 5、服务器阻塞,直到客户程序建立连接 */ sin_size=sizeof(struct s
6、ockaddr_in); if(new_fd=accept(sockfd,& server_addr,&sin_size)<0) { printf(“accept error!”); exit(-1); } //连接成功后打印客户端IP fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 将网络地址转换成.字符串 //6、read()函数读取客户端发送的消息 nbytes=read(new_fd,buff
7、er,1024);
buffer[nbytes]='\0';
printf("Server received %s\n",buffer);
/* 这个通讯已经结束 */
close(new_fd);
/* 循环下一个 */
}
/* 结束通讯 */
close(sockfd);
exit(0);
}
/********************完整程序*************************
#include 8、h>
#include 9、addr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
int nbytes;
char buffer[1024];
/*1、服务器创建sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) {
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* 2、服务器端填充 sockaddr结构 */
bzero 10、server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(portnumber);
/* 3、绑定地址结构体 */
if( bind(sockfd, (struct sockaddr *)(&server_addr),sizeof(struct sockaddr))<0)
{
11、
printf("bind error!");
exit(-1);
}
/* 4、设置监听允许连接的最大客户端数 */
if( listen( sockfd,5)<0)
{
printf("listen error!");
exit(-1);
}
while(1)
{
/* 5、服务器阻塞,直到客户程序建立连接 */
sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),& 12、sin_size))<0)
{
printf("accept error!");
exit(-1);
}
//连接成功后打印客户端IP
if(fork==0){ //fork(),创建子进程,多个进程不需等候
fprintf(stderr,"server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
//6、read()函数读取客户端发送的消息
nbytes=read(new_fd,buffer,1024);
buffer[nbyte 13、s]='\0';
printf("Server received %s\n",buffer);
/* 这个通讯已经结束 */
close(new_fd);
}
else close(new_fd); //结束父进程
/* 循环下一个 */
}
/* 结束通讯 */
return 0;
}
******************************/
2、编写客户端代码tcp_client.c
#include 14、errno.h>
#include 15、客户程序开始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) //AF_INET:Internet;SOCK_STREAM:TCP
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/* 2、客户程序填充服务端的资料 */
bzero(&server_addr,sizeof(server_addr)); // 初始化,置0
server_addr.sin_family=AF_INET; 16、 // IPV4
server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
/* 3、connect函数,客户程序发起连接请求 */
if(( connect( sockfd, &server_addr, sizeof(server_addr))<0)
{
printf(“connet error!”);
exit(-1);
}z
/* 连接成功了 */
pr 17、intf("Please input char:\n");
/* 等待终端输入数据 */
fgets(buffer,1024,stdin);
//4、write函数发送buffer数据
write( sockfd,buffer,strlen(buffer));
/* 结束通讯 */
close(sockfd);
exit(0);
}
/******************完整程序**************************
#include 18、rno.h>
#include 19、24];
struct sockaddr_in server_addr;
struct hostent *host;
/* 1、客户程序开始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
//AF_INET:Internet;SOCK_STREAM:TCP
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/* 2、客户程序填充服务端的资料 */
bzero(&s 20、erver_addr,sizeof(server_addr)); // 初始化,置0
server_addr.sin_family=AF_INET; // IPV4
server_addr.sin_port=htons(portnumber);
// (将本机器上的short数据转化为网络上的short数据)端口号
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
/* 3、connect函数,客户程序发起连接请求 */
if(connect( sockfd, (struct sockaddr *)(& 21、server_addr), sizeof(server_addr))<0){
printf("connet error!");
exit(-1);
}
/* 连接成功了 */
printf("Please input char:\n");
/* 等待终端输入数据 */
fgets(buffer,1024,stdin);
//4、write函数发送buffer数据
write( sockfd,buffer,strlen(buffer));
/* 结束通讯 */
close(sockfd);
exit(0);
return 22、 0;
}
************************************************/
3、编译并运行应用程序
先运行服务器程序tcp_server,然后在另一个终端中运行客户端程序tcp_client。从运行情况可以看出,在没有客户端连接上来时服务器程序阻塞在accept函数上,等待连接,当有客户端程序连接上来时,阻塞在read函数上,等待读取消息。客户端发送一条消息后结束,服务器读取消息并打印出来,继续等待新的连 23、接
l 上机报告要求:
上面的TCP服务器每次只能处理一次客户端请求,尝试将他改写成并发TCP服务器,即可以处理多个客户端请求,测试成功后将代码写入实验报告。
/***********************************************************
#include 24、tor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
domain:即协议域,又称为协议族(family)。
常用的协议族有,
AF_INET (IPv4 英特网域)、
AF_INET6 (IPv6英特网域)、
AF_LOCAL(或称AF_UNIX,Unix域socket)、
AF_ROUTE等等。
协议族决定了socket 25、的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,
SOCK_STREAM(流式套接字 TCP)、
SOCK_DGRAM(数据报套接字 UDP)、
SOCK_RAW、
SOCK_PACKET、
SOCK_SEQPACKET等等(socket的类型有哪些?)。
protocol:故名思意,就是指定协议。(按给定的域和套接字类型选择默认协议,通常为0)常用的协议有,
IPPROTO_TCP、
IPP 26、TOTO_UDP、
IPPROTO_SCTP、
IPPROTO_TIPC等,
它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
字节序转换
不同类型的CPU对变量的字节存储顺序可能不同:有的系统是大端字节序,即高位在低地址,即低位在高地址,而有 27、的系统如x86是小端字节序,即低位在低地址,高位在高地址,而网络传输的数据顺序是一定要统一的,所有当内部字节存储顺序和网络字节序(大端字节序)不同时,就一定要进行转换
unsigned long htonl(unsigned long hostlong);
unsigned short htons(unsigned short hostshort);
unsigned long ntohl(unsigned long netlong);
unsigned short ntohs(unsigned short netshort);
htonl()函数是将32位无符号长整形数据 28、从主机字节序转换为网络字节序
返回值:
htonl()返回一个网络字节顺序的值。
htons()函数是将16位无符号短整形数据从主机字节序转换为网络字节序
返回值:
htons()返回一个网络字节顺序的值。
htonl()/htons()函数是将32/16位整形数据从网络字节序转换为主机字节序
通用地址格式
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14字节的协议地址*/
};
sa_family:协议族,采用“AF_xxx”形 29、式,如AF_INET(IP协议族)(domain)
sa_data:1包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是夹杂在一起的
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 与struct sockaddr一样的长度 */
};
IP地址通常由数字加点(192.168.0. 30、1)的形式表示,而struct in_addr中使用的IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:
in_addr_t inet_addr(const char* strptr);
将一个点分十进制的IP转换成一个长整数型数(u_long类型)
char *inet_ntoa(struct in_addr in)
函数里面a代表ascii,n代表network
inet_aton是将a.b.c.d形式的IP转换为32位的IP,存储在inp指针里面,
inet_ntoa是将32位IP转换为a.b.c.d的格式
struct in_addr 31、{
unsigned long s_addr;
};
#include 32、
addrlen:对应的是地址的长度。(通常设为scokaddr结构的长度)
返回值:成功返回0,否则返回-1
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
#include 33、et地址(为指向sockaddr结构的指针),
addrlen:为socket地址的长度。
客户端通过调用connect函数来建立与TCP服务器的连接。
返回值:0成功,-1错误
#include 34、0成功,-1失败
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:为(被监听的)服务器的socket描述字,
addr:为指向struct sockaddr *的指针(指向sockaddr_in结构的指针,存放提出连接请求服务的主机IP和端口号信息),用于返回客户端的协议地址,
addrlen:为协议地址的长度。
返回值:成功,返回值是由内核自动生成的一个全新的描述字,进程可以通过这个新的描述符同客户进程传输数据。失败返回-1。
#include 35、h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include 36、d *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);






