Linux下C语言TCP编程01

#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;
}

发布了27 篇原创文章 · 获赞 12 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章