一步一步從原理跟我學郵件收取及發送 5.C語言的socket示例

    說到 C 語言版本的程序,首先要解決的問題就是兼容性. 做爲 20 年開發有 10 多年是在服務端的程序員,我深入地感覺到服務端平臺的兩極分化之嚴重,linux 派對 windows 那是超級的不屑一顧:那都是沒技術的人才用的,沒能力維護 linux 的人才用 windows. 與此同時 windows 派對 linux 也是嗤之以鼻,我曾經的一位經理就時常不屑對我說,我就不信那幾我的寫的東西能比公司寫的好. 奇怪的這兩派其實都很能掙錢,BAT 什麼的都用 linux 咱們就不說了,但股票期貨交易這樣重要的並且性能要求同樣很高的行業內幾乎一水的 windows + sql server 恐怕你們就不知道了吧. 因此我真不太贊成 linux 性能就比 windows 高的說法.

    我以爲造成這種說法的很重要的一個因素是不少高性能的軟件沒有 windows 版本,好比 nginx 長期不推薦在 windows 下使用, redis 下的 windows 版本竟然是微軟本身拿過來修改過才能用的.到底真相如何咱們就不討論了,單就爲何這些軟件沒有 windows 版本,我以爲一個很重要的緣由是 C/C++ 語言在兩種平臺下的兼容性問題.開源界如今大量的用 gcc,而 gcc 的語法如今和 vc 的語法差異是愈來愈大,我過去常常在 pc 中引用開源代碼,有些代碼花上一成天的無法在 vc 中編譯經過(印象中最好編譯的是 apache 的代碼).我我的以爲既然開源了,仍是應該考慮一下 vc 的兼容性(固然了我的時間是有限的,我寫的不少東西也都沒有考慮,基本上手上的平臺下能編譯過去也就算了...).

    這種兼容性體如今不少方面,第一步選擇 ide (或者稱不上 ide 的開發工具) 時基本上都會要求引入庫文件. linux 下是 so 或者 a 文件,這裏就不說了,單隻討論 windows 下的就有不少區別. 傳統 vc 下是要引入 lib 文件,而如今有大量基於 gcc 的多種開發工具,它們要引入的是 a 文件,它們是不通用的(小提示:有些版本的 gcc 能使用 lib 文件).因此若是是拿一個 vc 的示例,那麼在 gcc 系的開發工具中是用不了的. 個人解決辦法是不用 socket 的庫文件! 初學者還沒什麼,有經驗的同窗們又要炸鍋了:可能嗎! 沒什麼的可能的,前面已經說了這些 socket 函數是操做系統提供的,與開發語言無關,咱們其實能夠直接使用操做系統的功能,這種"直接使用"也沒什麼稀奇的就是直接調用 dll  文件罷了,delphi 的全部 socket 都是這樣使用的.具體的調用方法就是直接調用 dll 中的函數指針,這在全部的 windows api 開發書籍中都會講到,一點也不稀奇.本質上各個編譯器最後也是要這樣調用的,只不過它們按照傳統把這種操做弄到了庫文件中了而已.

我先上代碼,你們先別急着看,我後面會講解,其實也都挺簡單的.php

(文件名 socketplus.c)java

//一個方便測試 socket 程序的小文件,免得總是找 lib a 文件 
//目前是 gcc 專用,若是 vc 要用另外弄一個好了,不要在這上面弄條件編譯 


#ifndef _SOCKET_PLUS_C_
#define    _SOCKET_PLUS_C_

#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <winsock.h>
//#include <>

#include "lstring.c"

//#pragma comment (lib,"*.lib")
//#pragma comment (lib,"libwsock32.a")
//#pragma comment (lib,"libwsock32.a")


// 系統錯誤信息提示
void PrintError(DWORD last_err);

//--------------------------------------------------
//直接引入的 dll 函數 

//SOCKET PASCAL socket(int,int,int);
//char * (*fun1)(char * p1,char * p2);
#ifndef _MSC_VER
//不少同窗不會寫函數指針聲明//函數指針的寫法是,先寫正常的函數聲明,而後將函數名加上括號,而後在函數名前再加上*號便可!!! 
SOCKET PASCAL (*_socket)(int,int,int);
//SOCKET (PASCAL *_socket)(int,int,int); //vc 要這樣寫,vc6,vc2010 都是如此 //就是將 PASCAL 或者 stdcall 放到函數名的括號中 
int PASCAL (*_WSAStartup)(WORD,LPWSADATA);
unsigned long PASCAL (*_inet_addr)(const char*);
u_short PASCAL (*_htons)(u_short);
int PASCAL (*_connect)(SOCKET,const struct sockaddr*,int);
int PASCAL (*_WSAGetLastError)(void);
int PASCAL (*_send)(SOCKET,const char*,int,int);
int PASCAL (*_recv)(SOCKET,char*,int,int);
int PASCAL (*_select)(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*);
struct hostent * PASCAL (*_gethostbyname)(const char*);
#endif

#ifdef _MSC_VER
SOCKET (PASCAL *_socket)(int,int,int); //vc 要這樣寫,vc6,vc2010 都是如此//就是將 PASCAL 或者 stdcall 放到函數名的括號中 
int  (PASCAL *_WSAStartup)(WORD,LPWSADATA);
unsigned long  (PASCAL*_inet_addr)(const char*);
u_short  (PASCAL*_htons)(u_short);
int  (PASCAL *_connect)(SOCKET,const struct sockaddr*,int);
int  (PASCAL *_WSAGetLastError)(void);
int  (PASCAL *_send)(SOCKET,const char*,int,int);
int  (PASCAL *_recv)(SOCKET,char*,int,int);
int  (PASCAL *_select)(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*);
struct hostent *  (PASCAL *_gethostbyname)(const char*);
#endif

//-------------------------------------------------- 

int CreateTcpClient()
{
    //LoadLibrary("wsock32.dll");
    return _socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}

int InitWinSocket()
{
  WSADATA wData;

  //Result := WSAStartup(MakeWord(2, 2), wData) = 0;
  return _WSAStartup(MAKEWORD(2, 2), &wData);
  
}

int ConnectIP(SOCKET so, char * ip, int port)
{
  //sock: TSocket;
  //SockAddr: TSockAddr;
  //struct sockaddr SockAddr;
  struct sockaddr_in SockAddr;


  int err;
  int Result = 1;


  memset(&SockAddr, 0, sizeof(SockAddr));

  SockAddr.sin_family = AF_INET;
  SockAddr.sin_port = _htons(port);
  SockAddr.sin_addr.s_addr = _inet_addr(ip);

  //if (_connect(so, (struct sockaddr *)(&SockAddr), sizeof(SOCKADDR_IN)) == SOCKET_ERROR) 
  if (_connect(so, &SockAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) //實際上是不用轉換的 
  {
      PrintError(0);
    err = _WSAGetLastError(); //其實這個和 GetLastError 是同樣的
    //ShowMessageFmt('connect socket error,[%d]', [WSAGetLastError]);
    MessageBox(0, "connect socket error", "", 0);
    Result = 0;
    
    //int err = _WSAGetLastError();//若是前面調用了別的 api 這裏是取到 0 的,而不是錯誤信息碼 
    
    PrintError(err);
    
    if (INVALID_SOCKET == so) printf("connect error:INVALID_SOCKET\r\n");
    
    return 0;
  }
  
  return 1;

}//

//clq 這是我新加的函數,目的是能夠根據域名來訪問,而且原來的代碼只能訪問局域網

int ConnectHost(SOCKET so, char * host, int port)
{
    const char * address = host;
    int is_connect = 0;
    int err = 0; 
    
    // Create an address structure and clear it
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    
    // Fill in the address if possible//先嚐試當作IP來解析
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = _inet_addr(address);
    
    // Was the string a valid IP address?//若是不是IP就當作域名來解析
    if (addr.sin_addr.s_addr == -1)
    {
        // No, so get the actual IP address of the host name specified
        struct hostent *pHost;
        pHost = _gethostbyname(address);
    
        if (pHost != NULL)
        {
            if (pHost->h_addr == NULL)
                return 0;//false;
                
            addr.sin_addr.s_addr = ((struct in_addr *)pHost->h_addr)->s_addr;
        }
        else
            return 0;//false;
    }    

    addr.sin_port = _htons(port);
    
    //返回:-1 鏈接失敗;0 鏈接成功
    if (_connect(so, (struct sockaddr *)&addr, sizeof(addr)) == 0)
    {
        is_connect = 1;//true;
    }
    else
    {
        is_connect = 0;//false;
        //鏈接失敗
        //printfd3("WSAGetLastError:%d, WSAEWOULDBLOCK:%d\r\n", WSAGetLastError()-WSABASEERR, WSAEWOULDBLOCK-WSABASEERR);
        
          PrintError(0);
        err = _WSAGetLastError(); //其實這個和 GetLastError 是同樣的
        //ShowMessageFmt('connect socket error,[%d]', [WSAGetLastError]);
        MessageBox(0, "connect socket error", "", 0);
        is_connect = 0;
        
        //int err = _WSAGetLastError();//若是前面調用了別的 api 這裏是取到 0 的,而不是錯誤信息碼 
        
        PrintError(err);
        
        if (INVALID_SOCKET == so) printf("connect error:INVALID_SOCKET\r\n");        
    }
    
    return is_connect;
}//


#ifndef faveLoadFunction
#define faveLoadFunction
FARPROC WINAPI LoadFunction(HINSTANCE h, LPCSTR fun_name)
{
    FARPROC WINAPI r = 0;
    
    r = GetProcAddress(h, fun_name);
    
    if (r == 0) printf("load function %s error\r\n", fun_name);
    else printf("load function %s ok\r\n", fun_name);    

    return r;
}//
#endif


void LoadFunctions_Socket()
{
    //HINSTANCE hs = LoadLibrary("wsock32.dll"); //根據不一樣的編譯環境,有可能要從 LoadLibrary 改爲 LoadLibraryA
    HINSTANCE hs = LoadLibraryA("wsock32.dll"); //根據不一樣的編譯環境,有可能要從 LoadLibrary 改爲 LoadLibraryA
    
    if (hs == 0) printf("load wsock32.dll error\r\n", hs);
    else printf("load wsock32.dll ok\r\n", hs);
    
    _socket = GetProcAddress(hs, "socket");
    
    printf("_socket:%d\r\n", _socket);
    if (_socket == 0) printf("load _socket error\r\n", hs);
    
    //-------------------------------------------------- 
    //直接裝載各個 dll 函數 
    _socket = LoadFunction(hs, "socket");
    _WSAStartup = LoadFunction(hs, "WSAStartup");
    _inet_addr = LoadFunction(hs, "inet_addr");
    _htons = LoadFunction(hs, "htons");
    _connect = LoadFunction(hs, "connect");
    _WSAGetLastError = LoadFunction(hs, "WSAGetLastError");
    _send = LoadFunction(hs, "send");
    _recv = LoadFunction(hs, "recv");
    _select = LoadFunction(hs, "select");
    _gethostbyname = LoadFunction(hs, "gethostbyname");
    
    

}

// 系統錯誤信息提示
void PrintError(DWORD last_err)
{
    //進行出錯。
    //if (!CreateDirectory(_T("c:\\"),0))
    {
        char buf[512];//char buf[128];
        LPVOID lpMsgBuf;
        DWORD dw;
        
        memset(&buf, 0, sizeof(buf));
        
        dw = last_err;//GetLastError();
        
        if (dw == 0) dw = GetLastError(); //其實是能夠代替 WSAGetLastError 的 
        
        //dw = 5;//10035;//6000054;
        
        if (dw == 0) return;
        
        FormatMessage (
            FORMAT_MESSAGE_FROM_SYSTEM,//FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, //FORMAT_MESSAGE_ALLOCATE_BUFFER 是指要分配內存 
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &buf,//(LPTSTR) &lpMsgBuf,
            500,//0, //由於是本身分配的內存,因此要指出分配了多大 
            NULL );
            
        printf("PrintError(出錯碼=%d):%s\r\n", dw, buf); //奇怪,這裏就是不對//是倒數第 2 個 參數的問題 
            
        FormatMessage (
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, //FORMAT_MESSAGE_ALLOCATE_BUFFER 是指要分配內存 
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
            
         wsprintf(buf,
                    "出錯信息 (出錯碼=%d): %s",
                    dw, lpMsgBuf);             
            
        printf("PrintError(出錯碼=%d):%s\r\n", dw, lpMsgBuf);
        //printf("PrintError(出錯碼=%d):%s\r\n", dw, &buf); //奇怪,這個是不對的 
        printf("PrintError(出錯碼=%d):%s\r\n", dw, buf);
            
        LocalFree(lpMsgBuf);
        
        //輸出提示
        //OutputDebugString(szBuf);
    }
}//


//還沒有精測試,可能有誤
int SendBuf(SOCKET so, char * s, int len)
{
  int r = 0;
  int count = 0;
  char * p = s;

  //r := 0;
  int Result = 0;
  //count := 0;
  //p := @s[1];

  while (Result<len)
  {
    r = _send(so, p, len - Result, 0);
    if  (r > 0)
    {
      Result = Result + r;
      p = p + r;
      
    };


    count++;

    if (count>10) //超過多少次就不發送了 
    {
      MessageBox(0, "send error", "", 0);

      return Result;
    }

  }
  
  return Result;

}//

//注意,返回的字符串要本身釋放 
//lstring RecvBuf(SOCKET so)
//算了,仍是傳可自動釋放的字符串進去方便點 
//void RecvBuf(SOCKET so, lstring * buf)//用這個格式也能夠,不過與其餘語言不通用 
lstring * RecvBuf(SOCKET so, struct MemPool * pool)
{
  char buf[1024+1];
  int r = 0;
  //lstring s = String("");
  //lstring * s = NewString("", _buf->pool);
  //CheckPString(_buf);
  lstring * s = NewString("", pool);

  memset(&buf, 0, sizeof(buf));


  r = _recv(so, buf, sizeof(buf)-1, 0); //留下一個 #0 結尾
  if (r > 0) 
  {
    //SetLength(s, r);
    //Move(buf, s[1], r);
    //s.Append(&s, StringConst(buf, r));
    LString_AppendCString(s, buf, r);
  }

  if (r == 0) //通常都是斷開了
  {
    MessageBox(0, "recv error.[socket close]", "", 0);

    //return String(""); //不該該再生成一個新的
    return s;
  }

  return s;

}//


//是否可讀取
int SelectRead(SOCKET so)
{
  fd_set fd_read; //fd_read:TFDSet;
  struct timeval timeout; // : TTimeVal;

  int Result = 0;

  FD_ZERO( &fd_read );
  FD_SET(so, &fd_read ); //個數受限於 FD_SETSIZE 

  timeout.tv_sec = 0; //
  timeout.tv_usec = 500;  //毫秒
  timeout.tv_usec = 0;  //毫秒
  
      //linux 第一個參數必定要賦值
    ////int r = ::select(socket_handle+1, &fd_read, NULL, NULL, &l_timeout);

  //if select( 0, &fd_read, nil, nil, &timeout ) > 0 then //至少有1個等待Accept的connection
  if (_select( so+1, &fd_read, NULL, NULL, &timeout ) > 0) //至少有1個等待Accept的connection
    Result = 1;
    

  return Result;    

}//


#endif

 



固然,我這些代碼倒也不是爲了寫示例方便你們測試,而是由於工做的關係我須要在多個 ide 和多種編譯器中切換,早就寫好用來測試網絡應用的(主要是xmpp協議,因此之後咱們也順便介紹一下 xmpp 協議的一些簡單實現).

其中有如下幾點須要說明一下:
1.函數須要從 dll 中取出其地址,在代碼中實現爲 LoadFunctions_Socket();
2.LoadLibraryA/LoadLibrary 函數能夠理解爲將一個 dll 中的代碼讀取到程序的內存中;
3.GetProcAddress 函數能夠理解爲找到一個函數的地址;
4.不能直接使用原始的 socket 函數名,要使用時,在前面加一個劃線;
5.只引入了我用到的少數幾個 socket 函數,有須要的網友能夠本身引入:方法是先聲明一個同原型的函數指針,而後加入到 LoadFunctions_Socket() 中就能夠了.
6.代碼主要工做在 windows 版本的 gcc, vc6 和 vc2010 下也作過測試. 隨着代碼的增長 vc 下若是不能運行,你們能夠本身改下函數指針的聲明和 api 函數的版本(換下 a 和 w 的版本). 至於 a/w 版本是什麼意思,不懂的同窗咱們之後再說明吧.
7.我直接 include 了 c 的文件,方便在各個開發工具中切換,不喜歡這種方式的同窗請本身改爲 h 文件的方式吧.


我主要的工做環境是 cfree,緣由主要是 vc 產生的臨時文件實在是太大了,設置不產生臨時文件又會產生別的問題,而用 dev c++ 這些傳統的 gcc 環境的話,代碼提示又是一個問題. 找了好久我最終選用了 cfree 軟件,不過要說明的是 cfree 是收費的,但費用不足百元,因此雖然網上有不少註冊碼能夠用我仍是推薦你們註冊一下.遺憾的是,國內的共享環境之惡劣致使我付費時非常花了幾天時候,最終是找做者要到了他的淘寶帳號直接轉的錢,做者一度覺得我是騙子讓我去聯繫第三方的註冊網站,我以爲那個第三方的註冊網站更象騙子,因此堅持直接轉給他.之因此我認爲第三方註冊機構更象騙子是由於我也寫過共享軟件 ...

cfree 對我來講有如下吸引力的優勢:
1.沒有太大的臨時文件;
2.代碼提示還不錯;
3.不須要創建工程文件;
4.註冊費用低,我是它的正版用戶;
5.經過配置能夠把編輯器界面弄得象我喜歡的 vscode;
6.我有一個 32 位的 windows 2003 工做環境,那裏用不了 vscode,也不用瞭如今最新的 vc;
7.做爲 gcc 系,幾乎不須要配置編譯環境.

這裏要提一下開源的 codelite,也是不錯的軟件,代碼提示也作得很好,但生成的臨時文件大了點,並且須要配置編譯環境(對初學者很致命),若是沒有 cfree 我就用它了.

另一定要說的是它們的調試都不好,若是有沒法理解的代碼,請打開您的 VC ... 不過我如今調試用的是我能找到的最簡便的網友簡化版的非官方版本 BCB2009 (其實人家叫 CodeGear C++ Builder 2009,bcb 的稱呼只是歷史緣由),由於這個版本安裝很是方便,要不我就用 bcb6 了.原版 bcb2009 是否方便我就不清楚了. 找不到簡化版本的同窗用最多見的 vc6 調試就好了,同樣好用.

好了,下面給出測試的 demo 代碼. c 語言中還有些要注意的字節對齊等問題也來不及說了,咱們下篇再說吧.linux

    gSo = CreateTcpClient();
    r = ConnectHost(gSo, "newbt.net", 25);

    if (r == 1) printf("鏈接成功!\r\n");

    s = NewString("EHLO\r\n", m);

    SendBuf(gSo, s->str, s->len);


    printf(s->str);
    s->Append(s, s);
    printf(s->str);

    s->AppendConst(s, "中文\r\n");

    printf(s->str);

    rs = RecvBuf(gSo, m); //注意這個並不僅是收取一行 

    printf("\r\nRecvBuf:\r\n");
    printf(rs->str);
    
    rs = RecvBuf(gSo, m); //注意這個並不僅是收取一行 
    printf("\r\nRecvBuf:\r\n");
    printf(rs->str);

這是通信過程,代碼並不複雜,你們能夠看到簡單封裝後並不複雜,因此按本身順手的封裝一下頗有必要.nginx

如下是完整代碼:c++

(文件名 socket_test1.c)git

#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <winsock.h>

#include "lstring.c"
#include "socketplus.c"
#include "lstring_functions.c"

//vc 下要有可能要加 lib 
//#pragma comment (lib,"*.lib")
//#pragma comment (lib,"libwsock32.a")
//#pragma comment (lib,"libwsock32.a")



//SOCKET gSo = 0;
SOCKET gSo = -1;

void main()
{
    int r;
    mempool mem, * m;
    lstring * s;
    lstring * rs;
    
    //--------------------------------------------------

    mem = makemem(); m = &mem; //內存池,重要 

    //--------------------------------------------------
    //直接裝載各個 dll 函數
    LoadFunctions_Socket();

    InitWinSocket(); //初始化 socket, windows 下必定要有 


    gSo = CreateTcpClient();
    r = ConnectHost(gSo, "newbt.net", 25);

    if (r == 1) printf("鏈接成功!\r\n");

    s = NewString("EHLO\r\n", m);

    SendBuf(gSo, s->str, s->len);


    printf(s->str);
    s->Append(s, s);
    printf(s->str);

    s->AppendConst(s, "中文\r\n");

    printf(s->str);

    rs = RecvBuf(gSo, m); //注意這個並不僅是收取一行 

    printf("\r\nRecvBuf:\r\n");
    printf(rs->str);
    
    rs = RecvBuf(gSo, m); //注意這個並不僅是收取一行 
    printf("\r\nRecvBuf:\r\n");
    printf(rs->str);

    //--------------------------------------------------
    
    Pool_Free(&mem); //釋放內存池 
    
    printf("gMallocCount:%d \r\n", gMallocCount); //看看有沒有內存泄漏//簡單的檢測而已  
    
    //-------------------------------------------------- 

    getch(); //getch().不過在VC中好象要用getch(),必須在頭文件中加上<conio.h> 

}

除了通信 demo 部分外,其餘代碼你們都不用細看,都是臨時的輔助封裝函數而已,用到生產環境中後果我可不負責(雖然我認爲改一改也是能夠用的).就當是拋磚引玉吧.程序員

代碼裏其實還用到了一個很重要的字符串類,不過也是來不及解說了,先給出代碼以下:
(文件名爲 lstring.c 和 lstring_functions.c,原本我是要放到 github 上的,不過估計也是沒時間維護了,因此直接貼一下吧)github

//沒辦法仍是得另定義一個字符串 

#ifndef _L_STRING_C_
#define    _L_STRING_C_

#include <stdio.h>
#include <malloc.h> //有些編譯,若是 malloc.h 在後面的話會報 malloc 函數衝突,解決辦法很簡單,把含有 malloc 的頭文件放前面,好讓咱們的 malloc 定義能覆蓋它就能夠了
#include <string.h>
//#include <time.h>
//#include <winsock.h>
#include <windows.h>
#include <time.h>
//#include <crt/eh.h> //聽說 MinGW終於支持使用seh實現c++ eh了 
//https://sourceforge.net/p/mingw-w64/mingw-w64/ci/18a7e88bcbe8bc0de4e07dac934ebf0653c4da7c/tree/mingw-w64-headers/crt/eh.h

int gMallocCount = 0;

//簡單的內存泄漏檢測
void * malloc_v2(size_t size)
{
    gMallocCount++;
    return malloc(size);
}

void * free_v2(void * p)
{
    gMallocCount--;
    free(p);
}

#define malloc malloc_v2
#define free free_v2

typedef struct LString _LString; //相互引用的提早聲明好象必須用 typedef

//內存池中的一項//算了, string 的自動釋放就很複雜了,仍是專一於 string 的吧
struct MemPool_Item{
    //void * data; //要釋放的數據,能夠是不一樣類型的
    _LString * data; //要釋放的數據,能夠是不一樣類型的

    //void (*FreeFunc)(struct MemPool_Item * item); //對應節點的釋放函數//太複雜,就當作原始的 free 函數釋放就好了 
    
    struct MemPool_Item * next;
};

//內存池,用於釋放一次函數過程當中分配的內存 
struct MemPool{

    struct MemPool_Item * Items;
    int Count;
    char * str;
    byte _const; //是不是隻讀的,若是是隻讀的就是從數組中構造的,不要釋放它 
    int id; //只是爲調試釋放而已 

};

struct LString{

    char * _init; //只是用來判斷是否進行了初始化,不可靠的簡單判斷,爲 0 就是初始化了

    int len;
    char * str;
    byte _const;  //是不是隻讀的,若是是隻讀的就是從數組中構造的,不要釋放它
    byte _malloc; //這個結構是否是 malloc 生成的,若是是還要 free 掉結構體自己
    struct MemPool * pool; //字符串所在的函數體自動釋放內存池,若是是 NULL 就是要手工釋放的
    //在操做字符串的臨時函數中可使用它


    void (*Append)(struct LString * s, struct LString * add);
    void (*AppendConst)(struct LString * s, char * add);


};

#define mempool struct MemPool 

//函數體臨時使用字符串時 
//#define USEPOOL struct MemPool _function_mem_ = makemem();

//#define ENDUSEPOOL Pool_Free(&_function_mem_);

//輸出打印的級別 

//最普通的錯誤信息 
#define printf_err printf
//最低級別的打印 
#define printf_err1
//#define printf_err1 printf

mempool makemem()
{
    mempool mem; 
    memset(&mem, 0, sizeof(mem));
    
    srand((unsigned) time(NULL)); //用時間作種,每次產生隨機數不同
    mem.id = rand(); //number = rand() % 101; //產生0-100的隨機數
    
    return mem;
}

mempool * newmempool()
{
    mempool * pmem; 
    pmem = malloc(sizeof(struct MemPool));
    memset(pmem, 0, sizeof(struct MemPool));
    
    srand((unsigned) time(NULL)); //用時間作種,每次產生隨機數不同
    pmem->id = rand(); //number = rand() % 101; //產生0-100的隨機數
    
    return pmem;
}

//太複雜,就當作一個字符串池,用在一個函數體結束時自動釋放這個過程當中產生的全部臨時字符串,相似於 php 的自動釋放原理  
//加入一個節點, pool 並不分配內存,只是把你們加到一個鏈表中統一釋放而已 //函數參數的定義的函數指針是同樣的 
void Pool_AddItem(struct MemPool * pool, void * p)
{
    struct MemPool_Item * item = NULL;
    _LString * s = NULL;

    if (pool == NULL) return;
    
    //-------------------------------------------------- 
    //簡單的驗證 
        
    s = p;//item->data; 
    
    if (s->_init != NULL) //簡單的初始化檢測
    {
        printf("Pool_AddItem: error 未初始化的字符串指針!!!\r\n");
    }     
    
    //-------------------------------------------------- 
    

    item = malloc(sizeof(struct MemPool_Item));
    memset(item, 0, sizeof(struct MemPool_Item));
    
    //item->FreeFunc = FreeFunc;
    item->data = p;
    
    
    //下面兩步是替換掉頭節點 
    item->next = pool->Items;
    pool->Items = item;
    
    pool->Count++;

}//

//釋放一大片,能夠作些簡單的檢測
void Pool_Free(struct MemPool * pool)
{
    struct MemPool_Item * item = NULL;
    int i = 0;
    
    _LString * s = NULL;
    
    for(i=0; i<pool->Count; i++)
    {
        printf_err1("Pool_Free:%d, %d\r\n", pool->Count, i);
        item = pool->Items;
        //item->FreeFunc(item->data);
        //item->FreeFunc(item); //這樣更清晰一點
        
        s = item->data; 
        
        if (s->_init != NULL) //簡單的初始化檢測
        {
            printf("Pool_Free: error 未初始化的字符串指針!!!\r\n");
        } 
        
        
        //free(item->data);
        free(s->str);

        if (s->_malloc == 1) //結構體自己也要釋放的話 
        free(s);
        
        
        pool->Items = pool->Items->next; //向下移動一個指針位置 
        free(item); //節點本身的內存也要釋放
    }
    
    pool->Count = 0;

}

//自動釋放 
//void autofree(struct MemPool * pool, void * p)
//{
//    Pool_AddItem(pool, p);
//}

void freemempool(mempool * pool)
{

    Pool_Free(pool);

    free(pool);
}

#define freemem Pool_Free




struct LStringRef{

    //const struct LBuf * buf; //這個是保存內存內容的地方,不該該變 //本意是讓這個指針值固定,但這樣致使裏面的值也變不了 
    struct LString * buf; //這個是保存內存內容的地方,不該該變 //C 語言的特色,爲了在傳遞參數時不使用指針,只能是把指針放到成員中 
    
    void (*Append)(struct LString * s, struct LString * add);
    //int (*AppendConst)(struct LString * s, const char * add);

};



#define lstring struct LString 

#define PLString struct LString *

#define stringref struct LStringRef 

//#define GetStr  s.str 

//下面這幾個宏但是能夠用,不過太容易衝突了,最好是邏輯清晰度要求很高的地方纔用 
#define GetStr  buf->str 
#define GetLen  buf->len 

//#define str()  buf->str 
//#define len()  buf->len 

#define str__  buf->str 
#define len__  buf->len 



//爲了能自動釋放,只能是指針,結構體在參數傳遞時會丟失字段值,因此徹底的模仿 C++ 是不可能的 

//綁定各個成員函數
void BindStringFunctions(lstring * s);
//lstring String(char * str);


//自動釋放//只釋放字符串 
void autofree_s(struct MemPool * pool, struct LString * s)
{
    if (s == NULL) return;
    
    s->pool = pool; //加上這個標誌,這樣根據這個 s 操做出來的 string 均可以經過它自動釋放了 

    //Pool_AddItem(pool, p);
    //Pool_AddItem(pool, s->str);
    Pool_AddItem(pool, s);
}

//自動釋放//不僅釋放字符串,連指針一塊兒釋放 
void autofree_pstring(struct MemPool * pool, struct LString * s)
{
    if (s == NULL) return;
    
    s->pool = pool; //加上這個標誌,這樣根據這個 s 操做出來的 string 均可以經過它自動釋放了 

    Pool_AddItem(pool, s);
    //Pool_AddItem(pool, s->str);
}

//
////分配並返回一個字符串 
//stringref MakeString(char * str)
//{
//    stringref s;
//    
//    s.buf = malloc(sizeof(struct LString));
//    memset(s.buf, 0, sizeof(struct LString));
//    
//    s.buf->_const = 0;
//    s.buf->len = strlen(str);
//    s.buf->str = NULL;
//    
//    if (str != NULL)
//    {
//        s.buf->str = malloc(s.buf->len + 1); //還要留最後 \0 的位置 
//        memset(s.buf->str, 0, s.buf->len + 1);
//        
//        strcpy(s.buf->str, str);
//    }
//    
//    //綁定各個成員函數
//    //BindStringFunctions(&s);
//    
//    return s;
//
//}//
//

////分配並返回一個字符串指針
//lstring * PString(lstring s)
//{
//    //lstring s = String(str);
//    
//    lstring * p = malloc(sizeof(struct LString));
//    
//    *p = s;
//    p->_malloc = 1; //要釋放結構體自己 
//    
//    autofree_pstring(s->pool, p);
//    
//    return p;
//
//}//
//


////釋放 //pfree 是否釋放指針自己 
//void FreeStringRef(stringref * s, int pfree)
//{
//    if (s->buf == NULL || s->buf->str == NULL)
//    {
//        printf("FreeString() error: string is NULL");
//    
//        return;
//    }
//    
//    if (s->buf->_const != 0)
//    {
//        printf("FreeString() error: string is readonly");
//    
//        //return;
//    }
//    else
//    {
//        free(s->buf->str);
//        s->buf->str = NULL;
//    }
//    
//    free(s->buf);
//    
//    if (pfree)
//        free(s);
//
//}//

void _FreeString(lstring * s, int pfree)
{
    if (s == NULL)
    {
        printf("FreeString() error: lstring * s is NULL");
    
        return;
    }
    
    if (s->str == NULL)
    {
        printf("FreeString() error: s->str is NULL");
    
        return;
    }
    
    if (s->_const != 0)
    {
        printf("FreeString() error: string is readonly");
    
        //return;
    }
    else
    {
        free(s->str);
        s->str = NULL;
    }

    
    if (pfree)  //是否釋放指針自己
        free(s);

}//

void FreeString(lstring s)
{
    _FreeString(&s, 0); //只釋放數據 
}

void FreePString(lstring * s)
{
    _FreeString(s, 1); //釋放數據和指針
}

////釋放//給內存池調用的 
//void FreeString_ForPool(struct MemPool_Item * poolitem)
//{
//    //void * p = poolitem->data;
//    lstring * p = poolitem->data;
//    
//    if (p == NULL) return;
//
//    printf("準備釋放:%s\r\n", p->str);
//    
//    
//    FreePString(p);    
//
//
//}//
//

//分配一個內存池釋放的 
//lstring * StringPool(struct MemPool * pool, char * str)
//{
//    
//    lstring * s = PString(str);
//    Pool_AddItem(pool, s, FreeString_ForPool);
//
//    return s;
//}

//分配一個內存池釋放的 
//stringref StringRef(struct MemPool * pool, char * str)
//{
//    
//    lstring * s = PString(str);
//    Pool_AddItem(pool, s, FreeString_ForPool);
//
//    return *s;
//}


//用結構體就作不了自動釋放,因此仍是用指針吧,畢竟不是 C++ 
//lstring String(char * str, struct MemPool * pool)
//{
//    
//    lstring s;
//    
//    memset(&s, 0, sizeof(struct LString));
//    
//    s.len = strlen(str);
//    s.str = malloc(s.len+1);
//    memset(s.str, 0, s.len+1);
//    
//    memcpy(s.str, str, s.len);
//    
//    //綁定各個成員函數
//    BindStringFunctions(&s);
//    
//    autofree_s(pool, s);    
//
//    return s;
//}


//各個經常使用函數中儘可能使用這個函數分配新字符串,由於它生成的可自動釋放,避免 autofree 滿天飛
//這個應該是基礎函數,不要使用其餘函數實現 
lstring * NewString(char * str, struct MemPool * pool)
{
    
    //lstring s = String(str);
    lstring * p = malloc(sizeof(struct LString));
    
    memset(p, 0, sizeof(struct LString)); 
    
    p->_malloc = 1;
    
    p->len = strlen(str);
    p->str = malloc(p->len+1);
    memset(p->str, 0, p->len+1);
    
    memcpy(p->str, str, p->len);
    
    //綁定各個成員函數
    BindStringFunctions(p);    

    autofree_pstring(pool, p);
    

    return p;
}

//通常用於參數傳遞
lstring StringCopy(lstring str)
{
    
    lstring s;
    
    memset(&s, 0, sizeof(struct LString));
    
    s.len = str.len;

    s.str = malloc(s.len+1);
    memset(s.str, 0, s.len+1);
    
    memcpy(s.str, str.str, str.len);
    
    //綁定各個成員函數
    BindStringFunctions(&s);    
    

    return s;
}



//複製一個字符串給另一個內存池,若是對方爲 NULL 那麼就變成自由的字符串了,不過最好是不要這樣作,應當從設計上要求每一個字符串生成時都有medh池 
lstring * PStringCopyToPool(lstring * s, struct MemPool * pool)
{
    
    lstring * p = malloc(sizeof(struct LString));
    
    memset(p, 0, sizeof(struct LString));
    
    p->_malloc = 1; //結構體自己是分配的內存,也要釋放 
    
    p->len = s->len;

    p->str = malloc(s->len+1);
    memset(p->str, 0, s->len+1);
    
    memcpy(p->str, s->str, s->len);
    
    //綁定各個成員函數
    BindStringFunctions(p);    
    
    //if (autofree == 1) autofree_pstring(s->pool, p);
    autofree_pstring(pool, p);

    return p;
}//

//複製一個字符串 
//lstring * PStringCopy(lstring * s, int autofree)
lstring * PStringCopy(lstring * s)
{
    return PStringCopyToPool(s, s->pool);
    
//    lstring * p = malloc(sizeof(struct LString));
//    
//    memset(p, 0, sizeof(struct LString));
//    
//    p->_malloc = 1; //結構體自己是分配的內存,也要釋放 
//    
//    p->len = s->len;
//
//    p->str = malloc(s->len+1);
//    memset(p->str, 0, s->len+1);
//    
//    memcpy(p->str, s->str, s->len);
//    
//    //綁定各個成員函數
//    BindStringFunctions(p);    
//    
//    //if (autofree == 1) 
//    autofree_pstring(s->pool, p);
//
//    return p;
}//


//不分配內存,只是將一個緩衝區按 string 方式操做而已,相似 golang 的 bytes ,因此不要釋放它 
//這個也是基礎函數 ,雖然它的內存不用釋放,但也仍是要傳 pool ,以便給生成的子字符串自動釋放的機會 
lstring StringConst(char * str, int len, struct MemPool * pool)
{
    lstring s;
    

    //s._const = 0;
    
    s._const = 1;
    s.len = len; //strlen(str);
    s.str = str; //malloc(s.len);
    
    s.pool = pool;
    

    //綁定各個成員函數
    BindStringFunctions(&s);
    
    return s;

}//


void LString_Append(lstring * _s, lstring * _add)
{
    lstring s = (*_s); //犧牲一點點性能來換語法//golang 就沒有 -> 
    lstring add = (*_add);//犧牲一點點性能來換語法//golang 就沒有 -> 
    
    
    char * tmp = malloc(s.len + add.len + 1); //還要留最後 \0 的位置 
    memset(tmp, 0, s.len + add.len + 1);
    
    memcpy(tmp, s.str, s.len);
    memcpy(tmp + s.len, add.str, add.len); //這裏要注意//加上後半段 
    
    free(s.str);

    s.str = tmp;
    s.len = s.len + add.len;
    
//    char * tmp = malloc(s->len + add->len + 1); //還要留最後 \0 的位置 
//    memset(tmp, 0, s->len + add->len + 1);
//    
//    memcpy(tmp, s->str, s->len);
//    memcpy(tmp + s->len, add->str, add->len); //這裏要注意//加上後半段 
//    
//    free(s->str);
//
//    s->str = tmp;
//    s->len = s->len + add->len;    


    *_s = s;
}//



//加入傳統 C 字符串
void LString_AppendCString(lstring * s, char * str, int len)
{
    lstring add = StringConst(str, len, s->pool);

    printf("add len:%d", add.len);
    LString_Append(s, &add);
    
}//

void LString_AppendConst(lstring * s, char * add)
{

    //printf("add len:%d", add->len);
    LString_AppendCString(s, add, strlen(add));
    
}//

//int LString_AppendConst(lstring * _s, const char * _add)
//{
//    printf("ok4 \r\n");
//    lstring add = StringConst((char *)_add, strlen(_add));
//    
//    printf("ok1");
//    LString_Append(_s, &add);
//    printf("ok");
//}//

//檢查字符串指針是否合法,只是簡單的方法,不可靠,但有必定做用 
int CheckPString(lstring * s)
{
    if (s == NULL)
    {
        printf("CheckPString: error, string is NULL!!!\r\n"); //就目前的要自動釋放的需求來講,是不能爲 NULL 的,由於那樣 pool 就沒有傳入了 
        return 0;
    }
    printf_err1("CheckPString: s->_init != NULL\r\n");
    if (s->_init != NULL) //其實對於現代的編譯器和操做系統來講,若是 s 沒有初始化,在這裏就極可能崩潰,因此其實是檢測不出來的,因此首尾都打印一下看看這個過程沒結束就是這裏出錯了,對性能要求高的地方,去掉 printf 宏就能夠不打印了
    {
        printf("CheckPString: error, string is not init!!!\r\n"); //就目前的要自動釋放的需求來講,是不能爲 NULL 的,由於那樣 pool 就沒有傳入了 
        return 0;
    }

    printf_err1("CheckPString: s->_init != NULL ok.\r\n");
    return 1;
}//



//綁定各個成員函數
void BindStringFunctions(lstring * s) 
{
    printf_err1("BindStringFunctions \r\n");
    s->Append = LString_Append;
    s->AppendConst = LString_AppendConst;

}//




#endif

 

//操做 lstring * 的各類底層函數 
//由於 lstring 包含了傳統 C 的 0 結尾,因此大部分能夠直接代用 C 的函數,固然最好參照 golang 重寫

#ifndef _L_STRING_FUNCTIONS_C_
#define    _L_STRING_FUNCTIONS_C_

#include <stdio.h>
#include <string.h>

#include "lstring.c"


//delphi 轉換方便函數//但 C 語言索引是從 0 開始,不是 d7 的從 1 開始,必定要注意
//查找子串位置 
//php 的 strpos 基本上也是這樣 
int pos(lstring * substr, lstring * s) 
{
    char * p = strstr(s->str, substr->str);

    if (p == NULL) return -1;

    return p - (s->str);
}

//substr(字符串,截取開始位置,截取長度)//從 0 開始 
//lstring * substr(lstring * s, int start, int len) 
lstring * substring(lstring * s, int start, int len) 
{
    lstring r;
    lstring * sub;
    char * p = NULL;
    
    //if (start + len > s->len) return NULL;//若是太多
    if (start + len > s->len) len = (s->len) - start;//若是太多返回後面的 
     
    p = s->str + start;
    
    r = StringConst(p, len, s->pool);
    //r = StringCopy(r); //StringConst 不分配內存的,因此要複製一個新的出來 
    
    //sub = PString(r);
    //autofree_pstring(s->pool, sub);//返回字符串跟着 s 一塊兒釋放 
    
    sub = PStringCopy(&r);
    
    return sub;

}

//字符串是否相等 //相似 java 的 equals
int str_equals(lstring * s1, lstring * s2)
{
    if (s1 == NULL && s2 == NULL) return 1;
    
    if (s1 == NULL || s2 == NULL) return 0; //二者不可能都爲空的狀況下有一個爲空,那就是不相等了 
    
    if (s1->len != s2->len) return 0; //長度不等確定也不是 
    
    if ( 0 == strncmp(s1->str, s2->str, s1->len) ) return 1;

    return 0;
}

int streq(lstring * s1, lstring * s2)
{
    return str_equals(s1, s2); 
}

int str_equals_c(lstring * s1, char * s2)
{
    
    if ( 0 == strcmp(s1->str, s2) ) return 1;

    return 0;
}

//3) 前加#,將標記轉換爲字符串.
//#define C(x) #x
//則C(1+1) 即 」1+1」.

//#define C(x) #x
#define C(x) 
#define C1(a1, x, a2) strcmp(a1, a2)

//int ee_-()
int t()
{
    C(==);
    C1("",==,""); //能夠這樣模擬一個 == 號出來 

}

//經常使用簡寫而已 
int seq(lstring * s1, char * s2)
{

    return str_equals_c(s1, s2);
}

//替換單個字符,在須要高效時使用,由於替換一長串比較慢 
//str_replace 
lstring * str_replace_ch(lstring * s, char ch, char newch)
{
    lstring * r = PStringCopy(s);
    
    int i;
    
    for (i=0; i<r->len; i++)
    {
        if (r->str[i] == ch)
            r->str[i] = newch;
    }
    
    return r;

}//


//主要用於判斷空字符串//delphi 風格 
int length(lstring * s)
{
    if (s == NULL) return 0;
    
    return s->len;
}

//delphi 風格 
int Length(lstring * s)
{
    return length(s);
}

//主要用於判斷空字符串//golang 風格 
int len(lstring * s)
{
    if (s == NULL) return 0;
    
    return s->len;
}

//轉換爲小寫,注意這個在中文下會有問題//爲了兼容字符串自動釋放,當參數爲空時只好返回空,要不找不到父節點 
lstring * lowercase(lstring * s)
{
    lstring * r = NULL;
    int i;
    if (s == NULL)
    {
        printf("lowercase: error, string is NULL!!!"); //就目前的要自動釋放的需求來講,是不能爲 NULL 的,由於那樣 pool 就沒有傳入了 
        return NULL;
    }
    
    //檢查字符串指針是否合法,只是簡單的方法,不可靠,但有必定做用 
    CheckPString(s);
    
    r = PStringCopy(s);
    //r->pool = s->pool; //讓原字符串釋放它
    //autofree_pstring(s->pool, r); //讓原字符串釋放它
    
    for (i = 0; i < r->len; i++) 
    {
        r->str[i] = tolower(r->str[i]);
    }
    
    //沒有返回值,確實有問題 
    
    return r;
}

//轉換爲大寫,注意這個在中文下會有問題//爲了兼容字符串自動釋放,當參數爲空時只好返回空,要不找不到父節點 
lstring * uppercase(lstring * s)
{
    lstring * r = NULL;
    int i;
    if (s == NULL)
    {
        printf("lowercase: error, string is NULL!!!"); //就目前的要自動釋放的需求來講,是不能爲 NULL 的,由於那樣 pool 就沒有傳入了 
        return NULL;
    }
    
    //檢查字符串指針是否合法,只是簡單的方法,不可靠,但有必定做用 
    CheckPString(s);
    
    r = PStringCopy(s);
    //r->pool = s->pool; //讓原字符串釋放它
    //autofree_pstring(s->pool, r); //讓原字符串釋放它
    
    for (i = 0; i < r->len; i++) 
    {
        r->str[i] = toupper(r->str[i]);
    }
    
    //沒有返回值,確實有問題 
    
    return r;
}


//與傳統 get_value 不一樣,這個的匹配結尾字符串爲查找互的第一個而不是最後一個
//應該不區分大小寫
//要注意 C 語言的字符串是從 0 起始,而原來 delphi 的是從 1 起始的,因此 d7 轉換過來的算法不能所有照搬的
lstring * get_value_first(lstring *s, lstring * b_sp, lstring * e_sp)
{
    
    //開始複製的位置
    int b_pos = 0;
    //複製結束的位置
    int e_pos = 0;
    lstring * ls;
    lstring * r = NULL;
    
    if (len(e_sp) == 0) e_pos = length(s);

    ls = lowercase(s);
    b_sp = lowercase(b_sp);
    e_sp = lowercase(e_sp);

    //檢查字符串指針是否合法,只是簡單的方法,不可靠,但有必定做用
    CheckPString(ls);
    CheckPString(b_sp);
    CheckPString(e_sp);
    //--------------------------------------------------

    b_pos = pos(b_sp, ls);

    //if (length(b_sp) == 0) b_pos = 1;
    if (length(b_sp) == 0) b_pos = 0;

    //if (b_pos == 0)
    if (b_pos == -1) //沒找到
    {
        r = NewString("", s->pool);
        //autofree_pstring(s->pool, r); //返回的字符串跟着 s 一塊兒釋放
        return r;
    };

    b_pos = b_pos + length(b_sp);
    r = substring(s, b_pos, length(s));
    //result = copy(s, b_pos, length(s));

    //--------------------------------------------------

    //e_pos = pos(e_sp, lowercase(r)) - 1;
    e_pos = pos(e_sp, lowercase(r));

    if (e_pos == -1) e_pos = length(r);

    //r = substring(r, 1, e_pos);
    r = substring(r, 0, e_pos);

    return r;
}

//c 語言的參數 
lstring * getValueFirst_c(lstring *s, char * b_sp, char * e_sp)
{
    return get_value_first(s, NewString(b_sp, s->pool), NewString(e_sp, s->pool));

}

//c 語言的參數 
lstring * get_value_first_c(lstring *s, char * b_sp, char * e_sp)
{
    return get_value_first(s, NewString(b_sp, s->pool), NewString(e_sp, s->pool));

}





#endif
相關文章
相關標籤/搜索