MinGW-w64 で Winsock

最近、MinGW-w64(Eclipse) で Winsock のプログラムを書いたので、 その時の作業メモを残しておくことにする。

準備

まず Makefile の修正を行う。
コンパイルオプションに -DUNICODE を、 リンクオプションに -lWs2_32 を追加する。

下にソースの一部を表示する。
最初に #define で Windows 7 以降のプラットフォームであることを指定する。 これをしないと呼び出せない関数があるためである。
次に winsock2.hws2tcpip.h をインクルードする。
なを、GUIプログラムの場合は windows.h をインクルードする訳だが、 winsock2.h の後にインクルードする。
そうしないとwindows.hの中で、winsock.hが呼ばれるので、 旧い Winsock の設定が有効になってまずいことになる(らしい)。

そして プログラムの最初に WSAStartup を読んで Winsock ライブラリーの初期化を行う。

//** Windows 7 以降
#define _WIN32_WINNT _WIN32_WINNT_WIN7

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <ws2tcpip.h>
//#include <windows.h>

       (中略)

int main(void) {
  int ec;
  WSADATA wsaData;
  
  ec=WSAStartup(WINSOCK_VERSION, &wsaData);
  if(ec){
      printf("Winsock の初期化に失敗しました %d\n",ec);
      return ec;
      }

  printf("<<%s:%s %x %x>>\n",wsaData.szDescription,wsaData.szSystemStatus
           ,wsaData.wVersion,wsaData.wHighVersion);

  if(server) testServer();else testClient();
      
  WSACleanup();     
  return EXIT_SUCCESS;
}

ソケットの作成

ソケットの作成にはアドレス情報構造体(ADDRINFOW)を使う。
ヒントとなるアドレス情報を設定して GetAddrInfoW を呼び出す。
この時、ホスト名とポート番号の文字列はUTF16で渡す。(面倒くさい)

うまくゆけば、その環境で使えるアドレス情報のリストが得られる。
あとはそのアドレス情報からソケットを作成する。

SOCKET createSocket()
{
  SOCKET xsock;
  ADDRINFOW hints;
  wchar_t wport[1000],whost[1000];
  char buf[100];
  int ec;

  memset(&hints, 0, sizeof(hints));
  hints.ai_flags=AI_CANONNAME;
  if(server) hints.ai_flags|=AI_PASSIVE;
  hints.ai_family = IPv6 ? AF_INET6:AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;
  
  MultiByteToWideChar(CP_UTF8, 0, host, -1, whost, 1000);
  sprintf(buf,"%d",port);
  MultiByteToWideChar(CP_UTF8, 0, buf, -1, wport, 100);
  
  ec= GetAddrInfoW(whost,wport, &hints, &aiList);
  if(ec) {
      ec=WSAGetLastError();
      printf("GetAddrInfo のエラー  %x",ec);
      return INVALID_SOCKET;
      }
  
  xsock = socket(aiList->ai_family, aiList->ai_socktype, aiList->ai_protocol);
  if(xsock==INVALID_SOCKET){
     ec=WSAGetLastError();
     FreeAddrInfoW(aiList);
     printf("ソケットの作成に失敗しました %x\n",ec);
     }
        
  return xsock;    
  }

サーバーの作成

まずアドレス情報に AI_PASSIVE を指定してソケットを作成する。
次に sockaddr_in6 にアドレスファミリー(IPv4 か IPv6)と ポート番号をアドレス情報からコピーする。 アドレスはオール0に設定する。
そして bind を呼び出す。
sockaddr_in の代りに sockaddr_in6 を使うのはIPv6と兼用するためである。
ネットを検索した時にアドレス情報を直接使って
bind(sock, aiList->ai_addr, (int)aiList->ai_addrlen);
とやっている例を見つけたんだが、これだとアドレスが 1 になって、 ループバックテストしかできないような気がする。

次に listen でクライアントからの受付を開始する。
accept クライアントからの要求で作成された socket を取得する。 以降はこのsocket でクライアントと通信する。
今回は send でメッセージの送信を行なっている。

サーバーのテスト

別のコマンドプロンプトから
>telnet localhost 7777
として接続のテストができる。

void testServer()
{
  SOCKET sock=createSocket(),asock;
  struct sockaddr_in6 adr;
  SOCKADDR_STORAGE client;
  int ec,adrSize = sizeof(client);
  const char* msg="I am Server!";

  if(sock==INVALID_SOCKET) return;
  
  memset(&adr,0,sizeof(adr));
  memcpy(&adr,aiList->ai_addr,4);
  ec=bind(sock,(sockaddr*)&adr, (int)aiList->ai_addrlen);
  if(ec){
      ec=GetLastError();
      printf("bind error %d\n",ec);
      goto term;}

  ec=listen(sock,SOMAXCONN);
  if(ec){
      ec=GetLastError();
      printf("listen error %d ",ec);	  
      goto term;
      }
      
  for(int i=0;i<3;i++){
    printf( "クライアントからの接続をまってます\n" );
    asock=accept(sock,(SOCKADDR *)&client , &adrSize);
    if(asock==INVALID_SOCKET){
      ec=GetLastError();
      printf("accept error %d\n",ec);
      goto term;;
      }

    printf( "クライアントからの接続を受け付けました。メッセージを送信します\n" );
    ec=send(asock,msg,strlen(msg),0);
    if(ec==SOCKET_ERROR){
      ec=WSAGetLastError();
      printf("送信 error %d\n",ec);
      goto term;
      }
     
    closesocket(asock);
    }
  printf( "サーバーを終了します。\n" );

term:;
  FreeAddrInfoW(aiList);
  closesocket(sock);
  }

クライアントの作成

サーバーと同様にソケットを作成する。
作成したアドレス情報と使って connect を呼び、 サーバーと接続する。
接続したらサーバーと通信を行う。 今回は上記のサーバーから recv で受信する。

void testClient()
{
  SOCKET sock=createSocket();
  int ec;
  char buf[110];

  if(sock==INVALID_SOCKET) return;
 
  ec=connect(sock, aiList->ai_addr, aiList->ai_addrlen);
  if(ec==SOCKET_ERROR){
      ec=WSAGetLastError();
      printf("接続のエラー %d\n",ec);
      goto term;
      }

  ec=recv(sock,buf,100,0);
  if(ec==SOCKET_ERROR){
      ec=WSAGetLastError();
      printf("読込のエラー %d\n",ec);
      goto term;
      }
  buf[ec]=0;   
  printf("受信しました [%s]\n",buf);
 
term:;
  FreeAddrInfoW(aiList);
  closesocket(sock);
  }
以上で簡単な説明を終ります。 ソースと実行ファイルは下からダウンロードできます。
実行の際は chcp 65001 で端末のコードを UTF-8にしておくこと。

ある程度実用的なプログラムを作成しようとすると、 スレッドとかノンブロッキンング通信を使う必要も 出て来ると思うので、皆さん頑張って下さい。

今回の作業では ここの記事とプログラムが役に立ちました。

ダウンロード

  戻る