funini.com 自由研究 C/C++ ソケット

ソケット

TCP/IP ついて、 とりあえずコピー & ペーストしたら動く、ソケット (socket, connect, bindとか) のサンプルプログラムです。 サーバとクライアントの両方を含んでいます。

使い方

ダウンロードして、コンパイルして、サーバとクライアントの二つを実行します。 同じマシンで二つ実行しても大丈夫です。 動作としては、サーバを先に立ち上げておき、それにクライアントが接続します。 接続されると、サーバはクライアントにデータを送ります。 送り終わると、双方接続を閉じて終了します。
$ gcc socket_sample.c -o socket_sample
ターミナルを二つ立ち上げて、一つで
$ ./socket_sample 8011
として、サーバを起動します。(8011はポート番号で、好きな番号を指定してください)
その上で、
$ ./socket_sample 8011 127.0.0.1
として、クライアントを起動してサーバに接続します。 8011 は先ほどと同じポート番号を指定してください。 127.0.0.1 というところは、サーバの IP アドレスを指定します。 サーバとクライアントを同じマシンで立ち上げる場合は、 127.0.0.1 で大丈夫です。
うまくいくと、データが 100回転送され、それぞれの転送の バンド幅が表示されます。

ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> 
#include <netinet/ip.h>
#include <netdb.h>

#define LEN 1024 * 1024 * 4 #define TRIALS 100

#define MY_RCVBUF 1024 * 10

/***************************************************/

/* basic network functions come here: */

/* サーバ・クライアント のモード*/ enum { MODE_SERVER = 10, MODE_CLIENT = 11, };

/* コマンドラインからの設定項目 */ struct config { int mode; int port; char *hostname; };

void error_exit(char cp){ / エラーを出力して終了 */ perror(cp); exit(1); }

double get_dtime(void){ /* 時間を double 型で返す */ struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); return (double)(tv.tv_sec) + (double)(tv.tv_usec) * 0.001 * 0.001; }

void usage(char cmd){ / 使い方を出力 */ printf("Usage: %s [port] ( [server_name] )\n", cmd); printf(" example: %s 5011 ... for server\n", cmd); printf(" %s 5011 10.0.127.13 ... for client\n", cmd); exit(1); }

void read_config(struct config *config, int args, char *argv){ / コマンドラインから設定を読み込む */ assert(config != NULL);

if(args == 2){ // only port is specified -> server mode config->mode = MODE_SERVER;
config->port = atoi(argv[1]); } else if(args == 3){ // only port is specified -> server mode config->mode = MODE_CLIENT; config->port = atoi(argv[1]); config->hostname = argv[2]; } else { usage(argv[0]); } }

/***************************************************/

int prepare_server_socket(int port){ /* サーバソケットの準備 */ int ret; int sock; struct sockaddr_in sa;

/* ソケット生成 */ sock = socket(PF_INET, SOCK_STREAM, 0);

/* bind: ソケットをポートに割り当てる*/ sa.sin_family = AF_INET; sa.sin_addr.s_addr = INADDR_ANY; sa.sin_port = htons(port); ret = bind(sock, (struct sockaddr*)&sa, sizeof(sa)); if(ret == -1) error_exit("bind");

/* listen: 待ち受けを始める */ ret = listen(sock, 5); if(ret == -1) error_exit("listen"); return sock; }

int close_server_socket(int server_sock){ /* ソケットのクローズ */ close(server_sock); }

int do_accept(int server_sock){ /* 接続を受け付け、ソケットを返す */ int sock = accept(server_sock, NULL, NULL); if(sock == -1) error_exit("accpet"); return sock; }

void close_sock(int sock){ /* ソケットを閉じる */ close(sock); }

int do_connect(char hostaddr, int port){ / サーバに connect する。/ / ホスト名ではなく、IP アドレスを指定してください。*/ int ret; int sock; struct sockaddr_in sa;

sock = socket(PF_INET, SOCK_STREAM, 0); sa.sin_family = AF_INET; printf("connect to %s\n", hostaddr); inet_aton(hostaddr, &(sa.sin_addr)); sa.sin_port = htons(port);

ret = connect(sock, (struct sockaddr *)&sa, sizeof(sa)); if(ret == -1) error_exit("connect"); return sock; }

void do_send(int sock, int size, char buf){ / size の量のデータを送る / / WAITALL を指定することで、指定した量送られるまで関数が返らない */ int ret = send(sock, buf, size, MSG_WAITALL); if(ret != size) error_exit("Send interrupted");
}

void do_recv(int sock, int size, char buf){ / size の量のデータを受信する / / WAITALL を指定することで、指定した量受け取るまで関数が返らない */ int ret = recv(sock, buf, size, MSG_WAITALL); if(ret != size) error_exit("Recv interrupted"); }

/* 受信バッファの値を設定 void set_rcvbuf(int sock, int bufsize){

int ret; int i; getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&i, 4); printf("original RCVBUF: %d\n", i);

ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&bufsize, sizeof(bufsize)); if(ret < 0) error_exit("setsockopt"); } */

/***************************************************/

void client_core(int sock){ int i; char buf[LEN]; double d0, d1, bandwidth;

// set_rcvbuf(sock, MY_RCVBUF);

for(i = 0; i < TRIALS; i++){ d0 = get_dtime(); do_recv(sock, LEN, buf); d1 = get_dtime(); bandwidth = (double)LEN * 8 / (d1-d0) / 1000 / 1000; printf("client: %d, bandwidth: %f Mbps\n", i, bandwidth); } }

void server_core(int sock){ int i; char buf[LEN];

for(i = 0; i < TRIALS; i++){ do_send(sock, LEN, buf); printf("server: %d\n", i); } }

/***************************************************/

int server(struct config cfg){ / 何回でも接続を受け付け、データを送信する */ int server_sock = prepare_server_socket(cfg->port); for(;;){ int sock = do_accept(server_sock); server_core(sock); close_sock(sock); } close_server_sock(server_sock); }

int client(struct config cfg){ / サーバに一回だけ接続し、データを受信し、終了する。*/ int sock = do_connect(cfg->hostname, cfg->port); client_core(sock); close_sock(sock); }

/***************************************************/

int main(int args, char **argv){ int mode; struct config cfg;

read_config(&cfg, args, argv);

switch(cfg.mode){ case MODE_SERVER: server(&cfg); break;

case MODE_CLIENT: client(&cfg); break;

default: assert(0); }

return 0; }

雑多な話題