一、实验目的 学习和掌握 Linux 下的 TCP 服务器基本原理和基本编程方法。 二、实验平台 Linux 操作系统 三、实验内容 编写 Linux 下 TCP 服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的 IP 地址、端口号,并向客户端发送字符串。 四、实验原理 使用 TCP 套接字编程可以实现基于 TCP/IP 协议的面向连接的通信,它分为服务器端和客户端两部分,其 主要实现过程如图1.1所示。\n\n图1.1 TCP 客户/服务器的套接字函数\n\n函数:为了执行网络输入输出, 函数获得一个文 1、socket 函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用 socket 函数获得一个文 件描述符。 件描述符。\n\n\r\n
----------------------------------------------------------------#include int socket(int family,int type,int protocol); 返回:非负描述字---成功 -1---失败\n\n----------------------------------------------------------------第一个参数指明了协议簇,目前支持5种协议簇,最常用的有 AF_INET(IPv4协议)和 AF_INET6(IPv6 协议); 第二个参数指明套接口类型, 有三种类型可选: SOCK_STREAM(字节流套接口)、 SOCK_DGRAM(数 据报套接口)和 SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。 函数: 建立了套接口后, 为这个套接字指明远程端的地址; 2、connect 函数:当用 socket 建立了套接口后,可以调用 connect 为这个套接字指明远程端的地址; 如果是字节流套接口, 就使用三次握手建立一个连接;如果是数据报套接口, 仅指明远 如果是字节流套接口,connect 就使用三次握手建立一个连接;如果是数据报套接口,connect 仅指明远 程端地址,而不向它发送任何数据。 程端地址,而不向它发送任何数据。 ----------------------------------------------------------------#include int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen); 返回:0---成功 -1---失败\n\n----------------------------------------------------------------第一个参数是 socket 函数返回的套接口描述字; 第二和第三个参数分别是一个指向套接口地址结构的 指针和该结构的大小。 这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。以 IPv4套接口地址结 构为例,它以“sockaddr_in”命名,定义在头文件;以下是结构体的内容:\n\n-----------------------------------------------------------------struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { uint8_t sin_len; /* 无符号的8位整数 */ sa_family_t sin_family; /* 套接口地址结构的地址簇,这里为 AF_INET */ in_port_t sin_port; /* TCP 或 UDP 端口 */ struct in_addr sin_addr; char sin_zero[8]; }; ------------------------------------------------------------------函数: 和协议端口,对于网际协议,协议地址是32位 地址或128 3、bind 函数:为套接口分配一个本地 IP 和协议端口,对于网际协议,协议地址是 位 IPv4地址或 地址或 地址与16位的 端口号的组合;如指定端口为0, 位 IPv6地址与 位的 TCP 或 UDP 端口号的组合;如指定端口为 ,调用 bind 时内核将选择一个临时端 地址与 地址, 地址。 口,如果指定一个通配 IP 地址,则要等到建立连接后内核才选择一个本地 IP 地址。 ------------------------------------------------------------------#include /* IPv4地址 */\n\n\r\nint bind(int sockfd, const struct sockaddr * server, socklen_t addrlen); 返回:0---成功 -1---失败\n\n------------------------------------------------------------------第一个参数是 socket 函数返回的套接口描述字; 第二和第第三个参数分别是一个指向特定于协议的地 址结构的指针和该地址结构的长度。 函数: 服务器调用, 4、listen 函数:listen 函数仅被 TCP 服务器调用,它的作用是将用 sock 创建的主动套接口转换成被动 套接口,并等待来自客户端的连接请求。 套接口,并等待来自客户端的连接请求。 ------------------------------------------------------------------#include int listen(int sockfd,int backlog); 返回:0---成功 -1---失败\n\n------------------------------------------------------------------第一个参数是 socket 函数返回的套接口描述字; 第二个参数规定了内核为此套接口排队的最大连接个 数。由于 listen 函数第二个参数的原因,内核要维护两个队列:以完成连接队列和未完成连接队列。未完 成队列中存放的是 TCP 连接的三路握手为完成的连接,accept 函数是从以连接队列中取连接返回给进程; 当以连接队列为空时,进程将进入睡眠状态。 函数: 服务器调用,从已完成连接队列头返回一个已完成连接,如果完成 5、accept 函数:accept 函数由 TCP 服务器调用,从已完成连接队列头返回一个已完成连接,如果完成 连接队列为空,则进程进入睡眠状态。 连接队列为空,则进程进入睡眠状态。 ------------------------------------------------------------------#include int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen); 回:非负描述字---成功 -1---失败\n\n------------------------------------------------------------------第一个参数是 socket 函数返回的套接口描述字; 第二个和第三个参数分别是一个指向连接方的套接口地址 结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可 以将第二和第三个参数置为空。 6、write 和 read 函数:当服务器和客户端的连接建立起来后,就可以进行数据传输了,服务器和客户端 、 函数:当服务器和客户端的连接建立起来后,就可以进行数据传输了, 用各自的套接字描述符进行读/写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读 写函 用各自的套接字描述符进行读 写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读/写函 写操作 进行接收和发送操作。 数 write()和 read()进行接收和发送操作。 和 进行接收和发送操作 函数用于数据的发送。 (1)write()函数用于数据的发送。 ) 函数用于数据的发送 ------------------------------------------------------------------#include int write(int sockfd, char *buf, int len); 回:非负---成功 -1---失败\n\n------------------------------------------------------------------参数 sockfd 是套接字描述符,对于服务器是 accept()函数返回的已连接套接字描述符,对于客户端是调用 socket()函数返回的套接字描述符;参数 buf 是指向一个用于发送信息的数据缓冲区;len 指明传送数据缓 冲区的大小。\n\n\r\n函数用于数据的接收。 (2)read()函数用于数据的接收。 ) 函数用于数据的接收 ------------------------------------------------------------------#include int read(int sockfd, char *buf, intlen); 回:非负---成功 -1---失败\n\n------------------------------------------------------------------参数 sockfd 是套接字描述符,对于服务器是 accept()函数返回的已连接套接字描述符,对于客户端是调用 socket()函数返回的套接字描述符;参数 buf 是指向一个用于接收信息的数据缓冲区;len 指明接收数据缓 冲区的大小。 7、 、 send 和 recv 函数: 函数: TCP 套接字提供了 send()和 recv()函数, 函数, 用来发送和接收操作。 和 函数 用来发送和接收操作。 这两个函数与 write() 函数很相似, 和 read()函数很相似,只是多了一个附加的参数。 函数很相似 只是多了一个附加的参数。 函数用于数据的发送。 (1)send()函数用于数据的发送。 ) 函数用于数据的发送 ------------------------------------------------------------------#include #include < sys/socket.h > ssize_t send(int sockfd, const void *buf, size_t len, int flags); 回:返回写出的字节数---成功 -1---失败\n\n------------------------------------------------------------------前3个参数与 write()相同,参数 flags 是传输控制标志。 函数用于数据的发送。 (2)recv()函数用于数据的发送。 ) 函数用于数据的发送 ------------------------------------------------------------------#include #include < sys/socket.h > ssize_t recv(int sockfd, void *buf, size_t len, int flags); 回:返回读入的字节数---成功 -1---失败\n\n------------------------------------------------------------------前3个参数与 read()相同,参数 flags 是传输控制标志。\n\n五、实验步骤 1、登陆进入 ubuntu 操作系统,新建一个文件,命名为 tcpserver.c(为了方便起见,可以进入“home”,再 进入用户目录,在用户目录下新建 tcpserver.c) 。 2、在 tcpserver.c 中编写服务器端程序代码并保存。 3、在“终端”(“Applications”→“附件”→“终端”)中执行命令进入 tcpserver.c 所在目录。 (pwd 命令可以显 示当前所在目录;ls 命令可以显示当前目录下的文件和文件夹信息;cd..命令可以进入上一级目录;cd 目 录名 命令可以进入当前所示的某个目录。 ) 4、执行命令 gcc –o tcpserver tcpserver.c 生成可执行文件 tcpserver。 5、执行命令./ tcpserver,观察结果。 6、认真分析源代码,体会如何编写一个 TCP 服务器端程序。 六、参考程序(tcpserver.c) 参考程序( ) #include \n\n\r\n#include #include #include #include #include #include #include \n\n#define PORT 1234 #define BACKLOG 1\n\nint main() { int listenfd, connectfd; struct sockaddr_in server; struct sockaddr_in client; socklen_t addrlen; if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("Creating socket failed."); exit(1); } int opt =SO_REUSEADDR; setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr.s_addr= htonl (INADDR_ANY); if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) { perror("Binderror."); exit(1); } if(listen(listenfd,BACKLOG)== -1){ /* calls listen() */ perror("listen()error\\n"); exit(1); } addrlen =sizeof(client);\n\n\r\nif((connectfd = accept(listenfd,(struct sockaddr*)&client,&addrlen))==-1) { perror("accept()error\\n"); exit(1); } printf("Yougot a connection from cient's ip is %s, prot is\n\n%d\\n
8、观察两个“终端”出现的结果。
9、认真分析源代码,体会如何编写一个TCP客户端程序。
六、参考程序(tcpclient.c)
#include#include #include#include#include#include#include#include#define PORT 1234
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, num;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in server;
if (argc!=2) {
printf("Usage:%s \\nexit(1);
}
if((he=gethostbyname(argv[1]))==NULL){
printf("gethostbyname()error\\n");
exit(1);
}
if((sockfd=socket(AF_INET, SOCK_STREAM, 0))==-1){
printf("socket()error\\n");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family= AF_INET;
server.sin_port = htons(PORT);server.sin_addr =*((struct in_addr *)he->h_addr);
if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))==-1){
printf("connect()error\\n");
exit(1);
}
if((num=recv(sockfd,buf,MAXDATASIZE,0)) == -1){
printf("recv() error\\n");
exit(1);
}
buf[num-1]='\\0';
printf("Server Message: %s\\n
close(sockfd);
return 0;
}
实验结果: