●实验目的:
学会Linux的socket套接字网络编程,熟悉使用TCP传输协议的网络编程流程。
●实验要求:
编写使用TCP协议的服务器程序和客户端程序。客户端向服务器发送字符串,服务器打印收到的字符串。
●实验器材:
硬件:PC机一台。
●实验步骤:
(1)编写服务器端代码tcp_server.c。
(2)编写客户端代码tcp_client.c。
(3)编译并运行应用程序。
先运行服务器程序tcp_server,然后在另一个终端中运行客户端程序tcp_client。从运行情况可以看出,在没有客户端连接上来时服务器程序阻塞在accept函数上,等待连接;当有客户端程序连接上来时,阻塞在read函数上,等待读取消息。客户端发送一条消息后结束,服务器读取消息并打印出来,继续等待新的连接。
●上机报告要求:
上面的TCP服务器每次只能处理一次客户端请求,可尝试将它改写成并发TCP服务器,即可以处理多个客户端请求,测试成功后将代码写入实验报告。
/***********************************************************
#include<sys/socket.h>
int socket(int domain,int type,int protocol)
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述符跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket时,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
domain:即协议域,又称为协议族(family)。
常用的协议族有:
AF_INET(IPv4因特网域);
AF_INET6(IPv6因特网域);
AF_LOCAL(或称AF_UNIX,Unix域socket);
AF_ROUTE等等。
协议族决定了socket的地址类型,在通信中必须采用对应的地址,如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;
IPPTOTO_UDP;
IPPROTO_SCTP;
IPPROTO_TIPC等。
它们分别对应TCP传输协议;UDP传输协议;STCP传输协议;TIPC传输协议。
当调用socket创建一个socket时,返回的socket描述符存在于协议族(address family,AF_XXX)空间中,但没有一个具体地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则当调用connect()、listen()时系统会自动随机分配一个端口。
字节序转换:不同类型的CPU对变量的字节存储顺序可能不同:有的系统是大端字节序,即高位在低地址,即低位在高地址,而有的系统(如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位无符号长整型数据从主机字节序转换为网络字节序。
返回值:htonl()返回一个网络字节顺序的值。
·htons()函数是将16位无符号短整型数据从主机字节序转换为网络字节序。
返回值:htons()返回一个网络字节顺序的值。
·ntohl()/ntohs()函数是将32/16位整型数据从网络字节序转换为主机字节序。(www.xing528.com)
通用地址格式:
sa_family:协议族,采用“AF_xxx”形式,如AF_INET(IP协议族)(domain)。
sa_data:包含了一些远程计算机的地址、端口和套接字的数目,它里面的数据是夹杂在一起的。
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.1)的形式表示,而struct in_addr中使用的IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:
in_addr_t inet_addr(const char* strptr);
将一个点分十进制的IP转换成一个长整数型数(u_long类型)。
char *inet_aton(struct in_addr in)
本函数将一个用in参数所表示的Internet地址结构转换成以“.”间隔的诸如“a.b.c.d”的字符串形式。请注意inet_ntoa()返回的字符串存放在WINDOWS套接口实现所分配的内存中。
struct in_addr {
unsigned long s_addr;
};
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
将一本地地址与一套接口捆绑。本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。当用socket()创建套接口后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。
函数的三个参数分别为:
sockfd:即socket描述字,它是通过socket()函数创建的,唯一标识一个socket。Bind()函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。
addrlen:对应的是地址的长度。通常设为scokaddr结构的长度。
返回值:成功返回0,否则返回-1。
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket。如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数的参数分别为:
sockfd:为客户端的socket描述字。
addr:为服务器的socket地址(为指向sockaddr结构的指针)。
addrlen:为socket地址的长度。
客户端通过调用connect() 函数来建立与TCP服务器的连接。
返回值:0成功,-1错误。
#include<sys/socket.h>
int listen(int sockfd, int backlog);
函数的参数分别为:
sockfd:为要监听的socket描述字。
backlog:为相应socket可以排队的最大连接个数。系统默认值为20,0表示无限制。
Socket()函数创建的socket默认是一个主动类型的,listen() 函数将socket变为被动类型,以等待客户的连接请求。
返回值:0成功,-1失败。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数的参数分别为:
sockfd:为(被监听的)服务器的socket描述字。
addr:为指向struct sockaddr *的指针(指向sockaddr_in结构的指针,存放提出连接请求服务的主机IP和端口号信息),用于返回客户端的协议地址。
addrlen:为协议地址的长度。
返回值:成功,返回值是由内核自动生成的一个全新的描述字,进程可以通过这个新的描述符同客户进程传输数据。失败则返回-1。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。