1. 程式人生 > >C語言實現一個簡單的伺服器

C語言實現一個簡單的伺服器

C/S結構流程圖

image

服務端

socket函式

為了執行網路I/O,一個程序必須做的第一件事情就是建立一個socket函式

/* family 表示協議族     AF_INET(IPv4協議)、AF_INET6(IPv6協議)、AF_LOCAL(Unix域協議)、AF_ROUTE(路由套接字)、AF_KEY(  金鑰套接字)
   type 表示套接字型別   SOCK_STREAM(位元組流套接字)SOCK_DGRAM(資料報套接字)SOCK_SEQPACKET(有序分組套接字)SOCK_ROW(原始套接字)
   protocol 表示傳輸協議 IPPROTO_TCP
(TCP傳輸協議)、IPPROTO_UDP(UDP傳輸協議)、IPPROTO_SCTP(SCTP傳輸協議) 若成功返回非負描述符,若出錯返回-1 */ int socket(int family, int type, int protocol);

bind函式

bind函式把一個本地協議地址賦予一個套接字,對於網際協議,協議地址就是IP加埠的組合

/* sockfd 初始化的套接字
   myaddr 協議地址
   addrlen 協議地址長度
   若成功返回0 出錯返回-1 */
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen)

這個函式的第二個引數是協議地址,注意,這個協議地址已經有定義好的結構體,使用IPv4套接字結構地址時候,地址結構體定義如下

struct sockaddr_in {
    uint8_t sin_len;    //結構體長度
    sa_family_t sin_family; //AF_INET
    in_port_t sin_port; //埠(16-bie)
    struct in_addr sin_addr; //IPv4地址(32-bit)
    char sin_zero[8]; //沒啥用,設定0即可
}

listen函式

listen函式僅有伺服器呼叫,它完成兩件事情:
1. 當使用socket函式建立一個套接字時,它被假設為一個主動套接字,也就是說,它是一個將傳送connect發起連線的客戶端套接字。當呼叫listen函式之後,它被轉成一個被動套接字,只是核心應該接受連線請求。所以,呼叫listen之後套接字由CLOSED狀態轉到LISTEN狀態
2. 這個函式規定核心應該為相應套接字排隊的最大連線數

//失敗時返回-1
int listen(int sockfd, int backlog)

backlog引數的設定其實是表示兩個佇列的總和,這兩個佇列分別是
1. 未完成連線佇列,在客戶端傳送一個SYN直到三次握手完成,都是這個狀態,SYN_RCVD狀態。

2. 已完成連線佇列,這個表示三次握手完成的狀態,ESTABLISHED狀態

accept函式

accept函式是由TCP伺服器呼叫,用於從已完成連線佇列的隊頭返回下一個已完成連線,如果已完成連線佇列為空,那麼程序進入睡眠模式

// sockdf 伺服器套接字莫描述符
// cliaddr 已連線的客戶端協議地址
// addrlen 已連線的客戶端協議地址長度
// 成功返回非負描述符,出錯返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

當accept成功時,返回值是由核心自動生成的全新描述符,代表與所返回的客戶端TCP連線。所以,在我們討論accept函式時,我們稱第一個引數為監聽套接字,它的返回值是已連線套接字,一個伺服器通常指建立一個監聽套接字(通常是80埠),核心為每個由伺服器程序接受的客戶端連線建立一個已連線套接字,當伺服器完成對某個給定的客戶端服務時,連線就會被關閉。
函式的第二個引數也是一個協議地址結構體,這個結構體和服務端協議地址是同一個結構體。我們可以不關心客戶端的協議,直接傳空,我們關係的是這個函式的返回值,因為它返回的是客戶端連線描述符,我們可以對這個描述符進行寫操作,從而實現給客戶端傳輸資料。

write函式

// sockfd socket檔案描述符
// buf 檔案內容
// count 內容長度
ssize_t write(int sockfd, const void * buff, size_t count);

完整的伺服器程式碼

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int listenfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);

    if(listenfd < 0){
        printf("socket error");
        return -1;
    }

    struct sockaddr_in svraddr;/*宣告一個變數,型別為協議地址型別*/
    svraddr.sin_family = AF_INET;/*使用IPv4協議*/
    svraddr.sin_port = htons(8887);/*監聽8887埠*/
    svraddr.sin_addr.s_addr = htonl(INADDR_ANY);/*繫結本機IP,使用巨集定義繫結*/

    if(bind(listenfd ,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr)) < 0){
        printf("bind error");
        return -1;
    }

    if(listen(listenfd, 20) < 0){
        printf("listen error");
        return -1;
    }

    struct sockaddr_in cliaddr;/*只是宣告,並沒有賦值*/
    memset(&cliaddr, 0, sizeof(cliaddr));
    socklen_t ret = sizeof(cliaddr);
    int sockfd= accept(listenfd, (struct sockaddr*)&cliaddr, &ret );

    if(sockfd < 0){
        printf("appect error");
        return -1;
    }

    char str[] = "Hello World";
    write(sockfd , str, sizeof(str));

    close(sockfd );
    close(listenfd);
}

客戶端

socket函式

客戶端要和服務端進行網路通訊,首先也必須呼叫socket函式,這裡的socket函式和服務端的一樣。

connect函式

TCP客戶端就是使用connect函式和服務端建立連線

// sockfd 客戶端TCP描述符
// sockaddr 服務端協議地址
// addrlen 服務端協議地址長度
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);

這個函式將觸發客戶端和服務端三次握手,函式的第一個引數sockfd表示客戶端返回的描述符,這裡不需要呼叫bind函式繫結埠,系統會自動分配。函式的第二個引數需要配置服務端IP和埠資訊,同樣有結構體規範這些資訊,結構體也是和服務端一樣使用sockaddr_in型別。

read函式

客戶端連線上伺服器之後返回的是一個Socket檔案描述符,既然是檔案描述符,就可以通過簡單的read函式獲取網路資料。

// sockdf 檔案描述符
// buf 檔案內容存放地址
// count 內容長度
ssize_t read(int sockfd,void *buf,size_t count)

完整的客戶端程式碼

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int sockfd= socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);

    if(sockfd== -1){
        printf("socket error");
        return -1;
    }

    struct sockaddr_in svraddr;
    svraddr.sin_family = AF_INET;/*使用IPv4協議*/
    svraddr.sin_port = htons(8887);/*需要連線的遠端伺服器埠*/
    svraddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要連線的遠端伺服器IP*/

    if(connect(sockfd, (struct sockaddr *)&svraddr, sizeof(servaddr))  < 0){
        printf("connect error");
        return -1;
    }

    char str[64];
    read(sockfd, str, 64);
    printf(str);

    close(sockfd);
}

迭代伺服器

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int listenfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);

    if(listenfd < 0){
        printf("socket error");
        return -1;
    }

    struct sockaddr_in svraddr;/*宣告一個變數,型別為協議地址型別*/
    svraddr.sin_family = AF_INET;/*使用IPv4協議*/
    svraddr.sin_port = htons(8887);/*監聽8887埠*/
    svraddr.sin_addr.s_addr = htonl(INADDR_ANY);/*繫結本機IP,使用巨集定義繫結*/

    if(bind(listenfd,(struct sockaddr *)&svraddr,sizeof(svraddr)) < 0){
        printf("bind error");
        return -1;
    }

    if(listen(listenfd, 20) == -1){
        printf("listen error");
        return -1;
    }

    for ( ; ; ){ 
        struct sockaddr_in cliaddr;/*只是宣告,並沒有賦值*/
        socklen_t cliaddr_size = sizeof(cliaddr);
        int sockfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_size );

        if(sockfd < 0){
            printf("appect error");
            return -1;
        }

        char str[] = "Hello World";
        sleep(3);//3秒之後再向客戶端傳送資料
        write(sockfd, str, sizeof(str));

        close(sockfd);
        /*close(listenfd);*/
    }
}

併發伺服器

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>

void doit(int sockfd);

int main()
{
    int listenfd= socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    pid_t pid;

    if(listenfd < 0){
        printf("socket error");
        return -1;
    }

    struct sockaddr_in svraddr;/*宣告一個變數,型別為協議地址型別*/
    svraddr.sin_family = AF_INET;/*使用IPv4協議*/
    svraddr.sin_port = htons(8887);/*監聽8887埠*/
    svraddr.sin_addr.s_addr = htonl(INADDR_ANY);/*繫結本機IP,使用巨集定義繫結*/

    if(bind(listenfd,(struct sockaddr *)&svraddr,sizeof(svraddr)) < 0){
        printf("bind error");
        return -1;
    }

    if(listen(listenfd, 20) < 0){
        printf("listen error");
        return -1;
    }

    for( ; ; ){    
        struct sockaddr_in cliaddr;/*只是宣告,並沒有賦值*/
        socklen_t cliaddr_size = sizeof(cliaddr);
        int sockfd= accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_size );

        if(sockfd < 0){
            printf("appect error");
            return -1;
        }

        if( (pid = fork()) == 0 ){
            close(listenfd);/*子程序不需要監聽,關閉*/
            doit(sockfd);/*針對已連線的客戶端套接字進行讀寫*/
            close(sockfd);/*處理完畢,關閉客戶端連線*/
            exit(0);/*自覺退出*/
        }    

        close(sockfd);
        /*close(listenfd);*/
    }
}

void doit(int sockfd){
    char str[] = "Hello World";
    sleep(3);//3秒之後再向客戶端傳送資料
    write(sockfd, str, sizeof(str));
}

相關推薦

C語言實現一個簡單伺服器

C/S結構流程圖 服務端 socket函式 為了執行網路I/O,一個程序必須做的第一件事情就是建立一個socket函式 /* family 表示協議族 AF_INET(IPv4協議)、AF_INET6(IPv6協議)、AF_L

c語言實現一個簡單的通訊錄

通訊錄的c語言實現原始碼 簡單通訊錄的實現還是包括三個原始檔,test.c(實現通訊錄主邏輯),txl.c(實現用到的各個函式),txl.h(存放txl中用到的各種標頭檔案與宣告)。 txl.h #ifndef __TXL_H__//**txl.h** #defi

C語言-------實現一個簡單的單向連結串列

編寫一個連結串列程式,在程式中實現簡單的功能#include <stdio.h> #include <stdlib.h> struct node{ int num; char name[20]; struct node* nex

C語言一個簡單的三子棋,實現玩家與電腦的對戰

原始碼: #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <windows.h> #include <time.h> /* 用 C 寫一個三子棋 */ //邏輯: //1. 畫

**c++基於tcp協議的socket程式設計實現一個簡單伺服器**

c++基於tcp協議的socket程式設計實現一個簡單伺服器 基於tcp的通訊,可以利益socket套接字實現。通訊,顧名思義需要伺服器和客戶端兩者進行資訊互動。 通過流程圖我們可以看到程式設計實現伺服器和客戶端的步驟大致相同,而伺服器則更為複雜一些。本文之給出了一個簡單的伺服器程式設計和

go 語言實現一個簡單的 web 伺服器

學習Go語言的一些感受,不一定準確。假如發生戰爭,JAVA一般都是充當航母戰鬥群的角色。一旦出動,就是護衛艦、巡洋艦、航母艦載機、預警機、電子戰飛機、潛艇等等浩浩蕩蕩,殺將過去。(JVM,數十個JAR包,Tomcat中介軟體,SSH框架,各種配置檔案...天生就是重量級的,

【原始碼剖析】tinyhttpd —— C 語言實現簡單的 HTTP 伺服器

    tinyhttpd 是一個不到 500 行的超輕量型 Http Server,用來學習非常不錯,可以幫助我們真正理解伺服器程式的本質。     看完所有原始碼,真的感覺有很大收穫,無論是 unix 的程式設計,還是 GET/POST 的 Web 處理流程

C語言實現一個簡單的佇列

1、佇列.h #include<stdio.h> #include<stdlib.h> #define N 100 //定義佇列最大多少個 #define datatype char //定義佇列的資料型別 struct

使用C語言實現一個虛擬機

doesn 寄存器 php 浪費 vid c11 machine 指向 編程語言 使用C語言實現一個虛擬機 2015-6-22 21:32| 發布者: joejoe0332| 查看: 2891| 評論: 0|原作者: leoxu, Serval, 社會主義好, los

C++11實現一個簡單的線程池

start art AI fun con var func iostream any 為了不讓手生,邊復習邊手擼了一個線程池,代碼量比較少,如下,用了一些C++11的實現,語言標準嘛,就是跨平臺的: thread_poo.h #ifndef _THREAD_POOL_ #

C語言實現Socket簡單通信

簡單 置0 tin led AS accep sin ive receive 服務端 #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h

c語言 實現一個函式,判斷一個數是不是素數

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

C語言一個簡單的掃雷小遊戲

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <windows.h> #include <time.h> /* 用 C 語言寫一個簡單的掃雷遊戲 */ // 1.寫一個遊戲選單 M

C語言實現簡單的2048小遊戲

網上解釋很多了,直接上程式碼吧,這個功能很簡單,易於學習,後期有時間會完善功能 #include<stdio.h> #include<stdlib.h> #include<string.h> #define Key_Up 0x4800

C語言實現一個函式,可以左旋字串中的k個字元

// 實現一個函式,可以左旋字串中的k個字元   例如:                 //ABCD左旋一個字元得到BCDA                 //ABCD左旋兩個字元得到CDAB    解題思路:                   1> 先理思

C語言三劍客--一個簡單的命令直譯器

這是C語言三劍客書(三本書,不記得是哪一個了)的一個簡單的C語言實現的的命令直譯器,加在微控制器軟體工程裡,用來除錯,還是比較靈活方便的。 自制命令如下 1. 返回晶片 的ID值 &GetID 12345 2. 設定比例調節器的值為 12 & 12

推箱子游戲使用C語言實現簡單例項

/* 1.遊戲實現步驟 1).遊戲一開始,就顯示遊戲地圖。 while(1) { 2).輸入小人的前進方向。 3).根據小人的前進方向,來移動小人。 } 2.根據步驟 搭建專案框架 */ #include <stdio.h> #includ

C語言實現一個鍵值對結構demo

主要思路是有兩個指標陣列,一個為key,一個為value,用索引一一對應實現一個key對應一個value。包括了增加和刪除,控制檯列印方法,現在仍有些指標指向記憶體類的bug需注意。這個程式嚴格來說還算不上雜湊。 #include<stdio.h>

socket例項C語言一個簡單的聊天程式

我們老師讓寫一個簡單的聊天軟體,並且實現不同機子之間的通訊,我用的是SOCKET程式設計。不廢話多說了,先附上程式碼: 伺服器端server.c #include <stdio.h> #include <stdlib.h> #include

c語言實現模擬FTP伺服器專案

下載原始碼後,直接可以在ubuntu中編譯執行:FTP伺服器程式功能:客戶端:1.輸入命令: help  檢視FTP伺服器所支援的所有命令2.輸入名:ls    檢視伺服器上可以下載的所有檔案列表3.輸入命令:get filename     下載伺服器中指定檔案到本地目錄中