#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
int main()
{
int ret = 0;
int addr_len;
int portnum = 5555;//端口号
char recvbuffer[1024];//接收数据缓冲区
int serversocket,clientsocket;//服务器调用socket()之后的描述符
struct sockaddr_in server_addr;//服务器套接字
struct sockaddr_in client_addr;//客户端套接字
/*-------------------------------结构体sockaddr_in的注释----------------------------
头文件:#include<netinet/in.h>
struct sockaddr_in
{
short sin_family;//Address family一般来说AF_INET(地址族)PF_INET(协议族)
unsigned short sin_port;//Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
struct in_addr sin_addr;//IP address in network byte order(Internet address)
unsigned char sin_zero[8];//Same size as struct sockaddr没有实际意义,只是为了跟SOCKADDR结构在内存中对齐;SOCKADDR结构体和该结构体意义一样,16个字节长度。
};
-----------------------------------------------------------------------------------*/
serversocket = socket(AF_INET,SOCK_STREAM,0);
/*----------------------------------socket()注释------------------------------------
socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。
#include <sys/socket.h>
int socket( int af, int type, int protocol);
af:一个地址描述。目前仅支持AF_INET格式,也就是说ARPA Internet地址格式。
type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。
常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0。
常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,
它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
如果协议protocol未指定(等于0),则使用缺省的连接方式。
---------------------------------------------------------------------------------*/
if(serversocket == -1)
{
perror("socket() failed!");//头文件#include<stdio.h>,打印上一步操作的错误信息
return -1; //main()函数返回-1,会导致程序退出。
//return和exit的差别就是前者是返回一个值给函数,退出该函数,而后者是属于系统级别的,将返回值给系统,整个进程退出。
}
//初始化服务器套接字,就是将该套接字内容置0;
bzero(&server_addr,sizeof(server_addr));
/* 头文件#include <string.h>
void bzero(void *s, int n);会将参数s所指的内存区域前n个字节,全部设为零值。*/
//填充服务器套接字server_addr
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnum);//头文件:#include <arpa/inet.h>,
//必须要采用网络数据格式-大端字节序,普通数字可以用htons()函数转换成网络数据格式的数字
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//设置服务器套接字的IP地址为特殊值INADDR_ANY,这表示服务器愿意接收来自任何网络设备接口的客户机连接。htonl()函数的意思是将主机顺序的字节转换成网络顺序的字节。
/*sin_addr类型是结构体:struct in_addr {in_addr_t s_addr;};
* 其中in_addr_t 一般为 32位的unsigned long.
* 其中每8位代表一个IP地址位中的一个数值.
* 例如192.168.3.144记为0xc0a80390,其中 c0 为192 ,a8 为 168, 03 为 3 , 90 为 144;
* 打印的时候可以调用inet_ntoa()函数将其转换为char *类型.
*/
//注释
/*绑定套接字,使用函数:
* int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
* 其头文件为:#include <sys/types.h>和#include <sys/socket.h>
* */
if(bind(serversocket,(struct sockaddr *)&server_addr,sizeof(server_addr))==-1)
{
perror("bind() failed!");
return -1;
}
/*开始监听
int listen( int sockfd, int backlog);头文件#include <sys/socket.h>
sockfd:用于标识一个已捆绑未连接套接口的描述字。
backlog:等待连接队列的最大长度。*/
if(listen(serversocket,10))
{
perror("listen() failed!");
return -1;
}
/*等待 客户端连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//调用accept函数后,会进入阻塞状态
//accept返回一个套接字的文件描述符,这样服务器端便有两个套接字的文件描述符,
//serverSocket和client_addr。
//serverSocket仍然继续在监听状态,client则负责接收和发送数据
//clientAddr是一个传出参数,accept返回时,传出客户端的地址和端口号
//addr_len是一个传入-传出参数,传入的是调用者提供的缓冲区的clientAddr的长度,以避免缓冲区溢出。
//传出的是客户端地址结构体的实际长度,因此此处必须定义一个addr_len变量
//出错返回-1*/
while(1)
{
printf("服务器等待连接请求......\n");
addr_len = sizeof(client_addr);
clientsocket = accept(serversocket, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len);
if(clientsocket == -1)
{
perror("accept() failed!");
return -1;
}
printf("client IP:%s\t Port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
//inet_ntoa ip地址转换函数,将网络字节序IP转换为点分十进制IP
//表达式:char *inet_ntoa (struct in_addr);
while(1)
{
//接受消息,特别注意第三个参数一定要小于recvbuffer的实际长度。
bzero(recvbuffer,sizeof(recvbuffer));
ret = recv(clientsocket,recvbuffer,sizeof(recvbuffer)-1,0);
if(ret < 0 )
{
perror("recv() failed!");
continue;
}
if(ret == 0 )
{
printf("recv() is null,客户端已经关闭,继续监听\n");
close(clientsocket);
break;
}
recvbuffer[ret]='\0';//如果上面recv()的第三个参数要是等于recvbuffer的长度,
//返回的ret也有可能等于这个长度,就会导致此处末尾加'\0出错'
printf("收到的消息长度:%d\n内容:%s\n",ret,recvbuffer);
if(strcmp(recvbuffer,"quit") == 0)
{
while(1)
{
printf("客户端:%s停止访问!\n服务器关闭,输入yes\t继续监听,输入no\n请输入你的选择(yes/no):",inet_ntoa(client_addr.sin_addr));
char *qstr = (char *)malloc(10*sizeof(char));
scanf("%s",qstr);
if(strcmp(qstr,"yes") == 0)
{
printf("服务器停止监听,关闭退出!\n");
close(clientsocket);
close(serversocket);
exit(0);
}
else if(strcmp(qstr,"no") == 0)
{
printf("服务器继续监听!\n");
close(clientsocket);
break;
}
else
{
printf("输入错误,请重新输入!\n");
}
}
break;
}
send(clientsocket,recvbuffer,ret,0);
}
}
return 0;
}