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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章