Socket Programming

BSD Sockets

เริ่มต้นตั้งแต่ระบบปฏิบัติการ Berkeley UNIX (BSD) รุ่น 4.2 โดย BSD ได้ถูกปล่อยออกสู่สาธารณะ ก็มีความสามารถในการรองรับรูปแบบการสื่อสารระหว่างโปรเซสและระบบเครือข่ายด้วยวิธีการแบบ socket communication ซึ่งแนวคิดของ Socket คือเพื่อให้การสื่อสารระหว่างโปรเซสดำเนินการผ่านระบบไฟล์ I/O โดยมีการใช้ file descriptor ร่วมกัน โดยทั่วไปแล้วตัวโปรเซสลูกที่เกิดขึ้นจะได้รับสืบทอดตัว file descriptors มาจากโปรเซสแม่ เฉกเช่นเดียวกับกลไกการทำงานของ pipe แต่การสื่อสารด้วยวิธีการแบบ pipe เองจะเป็นแบบทิศทางเดียว (single direction) และก็สื่อสารกันได้เพียงภายในเครื่องคอมพิวเตอร์เครื่องเดียวกันเท่านั้น

ดังนั้นระบบปฏิบัติการ Berkeley UNIX ก็ได้นำเสนอแนวคิดการสื่อสารระหว่างโปรเซสที่สามารถสื่อสารกันภายในเครื่องคอมพิวเตอร์เดียวกัน หรือต่างเครื่องคอมพิวเตอร์ได้แต่จะต้องอยู่ภายใต้ระบบเครือข่าย TCP/IP (TCP/IP networking) รวมทั้งยังรองรับการสื่อสารแบบสองทิศทาง (bidirectional) ได้เช่นกัน ดังนั้นเพื่อให้การสื่อสารสามารถคุยได้สองทิศทางระหว่างโปรเซสแม่และโปรเซสลูกภายในเครื่องคอมพิวเตอร์เดียวกัน สามารถทำได้โดยการเรียกใช้ฟังก์ชัน socketpair() ซึ่งเป็นฟังก์ชันที่ถูกให้ทำงานได้เพียงโดเมนเดียว ซึ่งถูกเรียกว่า UNIX domain โดยการการใช้ AF_UNIX (Address Format UNIX) และ SOCK_STREAM ที่อยู่ในไลบรารี sys/socket.h และไลบรารี sys/types.h ดังตัวอย่างโปรแกรมข้างล่างนี้

// simple_socket.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define DATA1 "This data is sent from a child process . . ."
#define DATA2 "This data is sent from a parent process . . ."

/*
 * This  program creates a  pair of connected sockets,
 * then forks and communicates over them.  This is very 
 * similar to communication with pipes, however, socketpairs
 * are  two-way  communications  objects.  Therefore I can
 * send messages in both directions.
 */

main() {
	int sockets[2], child;
	char buf[1024];

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
		perror("opening stream socket pair");
		exit(1);
	}

	if ((child = fork()) == -1)
		perror("fork");
	else if (child) { /* This is the parent. */
		close(sockets[0]);
		if (read(sockets[1], buf, sizeof(buf)) < 0)
			perror("reading stream message");
		printf("-->%s\n", buf);
		if (write(sockets[1], DATA2, sizeof(DATA2)) < 0)
			perror("writing stream message");
		close(sockets[1]);
	} else { /* This is the child. */
		close(sockets[1]);
		if (write(sockets[0], DATA1, sizeof(DATA1)) < 0)
			perror("writing stream message");
		if (read(sockets[0], buf, sizeof(buf)) < 0)
			perror("reading stream message");
		printf("-->%s\n", buf);
		close(sockets[0]);
	}
}
$ gcc -o simple_socket simple_socket.c 
$ ./simple_socket
-->This data is sent from a child process . . .
-->This data is sent from a parent process . . .

จากตัวอย่างโปรแกรมข้างต้นจะเห็นว่าการใช้ socketpairs (หรือแม้กระทั่ง pipes) นั้นถูกจำกัดในเรื่องของการสื่อสารข้อมูลระหว่างโปรเซสได้เฉพาะกลุ่มโปรเซสที่อยู่ในตระกูลเดียวกัน (โปรเซสแม่ที่มีการสร้างโปรเซสลูกเพิ่มขึ้นมา) โดยเมื่อไหร่ก็ตามที่มีการเกิดของแต่ละโปรเซสที่ไม่ได้อยู่ในตระกูลเดียวกันหรือโปรเซสที่ทำงานอยู่คนละเครื่องคอมพิวเตอร์แล้ว แต่ละโปรเซสจะสื่อสารกันทันทีไม่ได้ แต่จะต้องสร้าง socket ของตัวเองขึ้นมาเพื่อเอาไว้ส่งและรับข้อมูล โดยจะต้องมีการระบุชื่อ (name) ให้กับ socket เพื่อใช้ในการอ้างอิงถึงกัน เมื่อเริ่มใช้งานชื่อเหล่านั้นจะต้องถูกแปลงให้เป็นหมายเลขที่อยู่ (address) ซึ่งเลขที่อยู่ของแต่ละ socket จะถูกระบุให้อยู่ภายในพื้นที่หรือโดเมนเดียวกัน

ชื่อโดเมนสำหรับ socket มีอยู่หลายแบบ แต่ที่เป็นที่รู้จักและนิยมก็มีอยู่ 2 ประเภทคือ UNIX domain (AF_UNIX) และ Internet domain (AF_INET)

โดยเฉพาะในกรณีที่โปรเซสทำงานแยกกันอยู่คนละเครื่องคอมพิวเตอร์ที่ซึ่งถูกเชื่อมต่ออยู่บนระบบเครือข่ายอินเทอร์เน็ท (TCP/IP) ต้องการจะสื่อสารข้อมูลระหว่างกัน แต่ละโปรเซสจะต้องมีการสร้างชื่อสำหรับ socket ขึ้นมาภายใน Internet domain เดียวกันซึ่งถูกพัฒนาอยู่ในระบบปฏิบัติการ UNIX ที่มีการใช้โปรโตคอลมาตราฐานสำหรับระบบเครือข่ายที่ถูกกำหนดโดยหน่วยงาน DAPRA ได้แก่ IP, TCP และ UDP โดยที่หมายเลขที่อยู่ (address) ใน Internet domain นั้นจะประกอบไปด้วยที่อยู่เครือข่ายของเครื่อง (machine network address) และ หมายเลขพอร์ต (port address)

Socket นั้นมีอยู่ด้วยกัน 3 ประเภทโดยยึดตามมาตราฐานของเทคโนโลยี TCP/IP ได้แก่

Socketคำอธิบาย

Datagram Socket

เรียกอีกชื่อหนึ่งว่า Connection less Socket ซึ่งใช้โปรโตคอล UDP (User Datagram Protocol) เป็นตัวกำหนดวิธีการสื่อสาร โดยข้อมูลหรือแพ็กเก็ต (packet) แต่ละตัวจะถูกส่งบน datagram socket ที่แยกเส้นทางกันออกไป ดังนั้นแพ็กเก็ตที่ส่งจากเครื่องต้นทางก็จะถูกลำเลียงกระจายออกไปในแต่ละเส้นทาง จนถึงเครื่องรับปลายทาง โดยแต่ละแพ็กเก็ตก็อาจจะมาถึงเครื่องปลายทางแบบไม่ได้เรียงตามลำดับตามที่ถูกส่งออกจากเครื่องต้นทางก่อนหน้านั้น

Stream Socket

เรียกอีกชื่อหนึ่งว่า Connection-oriented Socket ซึ่งใช้โปรโตคอล TCP (Transport Control Protocol) เป็นตัวกำหนดวิธีการสื่อสาร ซึ่งจะมีการสถาปนาการเชื่อมต่อและการันตีการรับส่งแพ็กเก็ต ดังนั้นข้อมูลหรือแพ็กเก็ตแต่ละตัวจะถูกส่งบน stream socket และจะถูกลำเลียงส่งผ่านช่องทางที่ถูกสร้างขึ้นมานี้ไปยังเครื่องปลายทางจนครบถ้วนสมบูรณ์

Raw Socket

ส่วนใหญ่จะพบในอุปกรณ์เครือข่ายเช่น สวิทซ์ (Switch) และ เราเตอร์ (Router) ซึ่งทำงานอยู่ในระดับ Internet Layer ที่มีการรับส่งข้อมูลโดยไม่ได้มีการใช้โปรโตคอลเหมือน datagram และ stream socket ในการกำหนดมาตราฐานการสื่อสาร

เมื่อโปรเซสทั้งสองที่อยู่ต่างเครื่องคอมพิวเตอร์ต้องการสื่อสารระหว่างกันจะต้องมีสถาปนาการเชื่อมต่อด้วยขั้นตอนตามเทคโนโลยี TCP/IP ซึ่งอยู่ในระดับชั้น transport (transport layer) และจะต้องมีการระบุหมายเลขพอร์ต (port address) ไปยังโปรโตคอลแอพพิเคชั่น (application protocol) ที่โปรเซสแต่ละฝั่งใช้อยู่ด้วย ตัวอย่างเช่นโปรแกรมรับส่งไฟล์ที่ใช้โปรโตคอล FTP ในการกำหนดควบคุมวิธีการส่งไฟล์ระหว่างกัน เรียกการพัฒนาโปรแกรมทางด้านนี้ว่า socket programming

การพัฒนาโปรแกรมทางด้าน socket จะต้องทำความเข้าใจวิธีการตั้งค่าหมายเลข IP address แต่ละตัว ไม่ว่าจะเป็นหมายเลขเครือข่าย (network ID) หมายเลขเครื่อง (host ID) และหมายเลข netmask address

ความสัมพันธ์ระหว่างโปรแกรม, socket, protocol และหมายเลขพอร์ตภายในเครื่องคอมพิวเตอร์เพื่อใช้ในการสื่อสารข้อมูลระหว่างโปรเซสที่อยู่ต่างเครื่องกัน ประเด็นที่น่าสนใจที่นักพัฒนาควรรู้ได้แก่

  • โปรแกรมหนึ่งโปรแกรมสามารถเปิดซ็อกเก็ตได้หลายซ็อกเก็ตเพื่อรับการเชื่อมต่อจากเครื่องภายนอกได้พร้อมกันในเวลาเดียวกัน

  • โปรแกรมหลายโปรแกรมสามารถใช้ซ็อกเก็ตตัวเดียวกันในเวลาเดียวกันแต่ไม่ค่อยพบเห็นการใช้ในลักษณะนี้

  • มากกว่าหนึ่งซ็อกเก็ตที่สามารถถูกเกี่ยวข้องและใช้งานพอร์ตอันเดียวกัน

  • โปรแกรมประยุกต์แต่ละตัวจะมีการใช้ทั้ง TCP และ UDP เพื่อใช้ในการจัดการและรับส่งข้อมูล

ตามวัตถุประสงค์ที่แตกต่างกันไป เช่น ต้องการเน้นความเร็วในการส่งแม้ข้อมูลจะหายได้บ้างก็จะใช้ช่องทาง UDP หรือถ้าเน้นความถูกต้องของข้อมูลโดยที่ข้อมูลจะต้องไม่สูญหายก็จะใช้ช่องทาง TCP ในการรับส่งแทน

  • ไฟล์ /etc/services ภายในระบบปฏิบัติการลีนุกซ์จะบอกรายละเอียดของโปรแกรมให้บริการและโปรโตคอลที่ใช้หมายเลขพอร์ตในการสื่อสารผ่านซ็ิอกเก็ตในระดับของ Transport layer

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ไลบรารีที่เกี่ยวข้องคือ sys/types.h,sys/socket.h,unistd.h,netinet/in.hและsys/un.h

  • ข้อมูลตัวแปรที่สำคัญ

    • ตัวแปร struct sockaddr สำหรับระบุข้อมูลพื้นฐานของโดเมนทั้ง AF_UNIX และ AF_INET

    • ตัวแปร struct sockaddr_in สำหรับระบุคุณลักษณะของ socket ใน Internet domain โดยมีรายละเอียดดังนี้

struct sockaddr {
	unsigned short sa_family;    // address family, AF_xxx
	char sa_data[14];  // 14 bytes of protocol address
};

struct sockaddr_in {
	u_char sin_len;
	u_char sin_family; /* = AF_INET */
	u_short sin_port; /* use htons() to set this */
	struct in_addr sin_addr;
	char sin_zero[8]; /* 8 zeros required here */
};

struct in_addr {
	in_addr_t s_addr;
/* either use a macro from netinet/in.h (eg INADDR_ANY), or inet_addr */
};

  • ฟังก์ชัน int socket(int domain, int communication_type, int protocol);

    • เพื่อใช้สร้าง socket โดยระบุโดเมนที่ต้องการ (domain) เช่น AF_INET และระบุประเภทของการสื่อสาร (communication_type) เช่น SOCK_STREAM โดยโปรโตคอลก็ขึ้นอยู่กับชนิดของโดเมน

ประเภทของการเชื่อมต่อสื่อสารระหว่างเครื่องคอมพิวเตอร์ผ่าน socket นั้นแบ่งออกได้เป็น 2 ประเภทได้แก่

  1. การสื่อสารแบบมีการสถาปนาการเชื่อมต่อ (connection-oriented หรือ connectionful) หมายถึงก่อนที่จะมีการสื่อสารข้อมูลระหว่างกันทั้งสองเครื่องจะต้องมีการสถาปนาการเชื่อมต่อให้เสร็จเรียบร้อยก่อน (point-to-point) เพื่อให้การสื่อสารข้อมูลผ่านท่อนี้มีการส่งข้อมูลที่ครบถ้วนและมีถูกต้องของข้อมูลมากที่สุด โดยมีขั้นตอนคร่าวๆดังต่อไปนี้

    • สร้าง socket และระบุโดเมน -- socket()

    • ทำการผูกรายละเอียดของ socket และหมายเลขพอร์ต -- bind()

    • ทำการเปิดพอร์ตเพื่อรอการขอการเชื่อมต่อจากภายนอก -- listen()

    • เมื่อมีการร้องขอการเชื่อมต่อก็จะตอบรับและเตรียมสถาปนาการเชื่อมต่อระหว่างกัน -- accept()

    • เริ่มการสื่อสารข้อมูลระหว่างกัน -- write() and read()

    • สิ้นสุดการเชื่อมต่อ -- close()

  2. การสื่อสารแบบไม่ต้องมีการสถาปนาการเชื่อมต่อ (connectionless) หมายถึงเครื่องคอมพิวเตอร์สามารถสื่อสารข้อมูลออกไปได้ทันทีโดยที่ไม่ต้องมีการสถาปนาการเชื่อมต่อแต่อย่างใด นักพัฒนาโปรแกรมก็จะต้องหาวิธีการเทคนิคต่างๆเพื่อทำให้การสื่อสารข้อมูลถูกต้องด้วยตัวเอง โดยมีขั้นตอนคร่าวๆดังต่อไปนี้

    • สร้าง socket และระบุโดเมน -- socket()

    • ทำการผูกรายละเอียดของ socket และหมายเลขพอร์ต -- bind()

    • เริ่มการสื่อสารข้อมูลระหว่างกันได้ทันที -- sendto() and recvfrom()

    • สิ้นสุดการเชื่อมต่อ -- close()

โดยค่าปริยายแล้วการทำงานของ sockets จะเป็นในลักษณะแบบถูกกำหนดให้หยุดรอ (blocking) ดังนั้นฟังก์ชันต่างๆจะต้องถูกหยุดอยู่ชั่วขณะจนกว่างานที่ร้องขอบน socket จะสิ้นสุดลง แต่อย่างไรก็ตามก็สามารถตั้งค่าให้ socket ทำงานแบบ non-blocking ได้โดยการใช้ฟังก์ชัน fcntl

ตัวอย่างที่ 1

ตัวอย่างโปรแกรมสื่อสารผ่าน socket ในลักษณะการเชื่อมต่อแบบ connectionless (SOCK_DGRAM) โดยเครื่องลูก (datagram client) จะเชื่อมต่อไปยังเครื่องแม่ (datagram server) แล้วทำการส่งข้อความไปยังเครื่องแม่ เมื่อเครื่องแม่ได้รับข้อความก็จะแสดงออกทางหน้าจอของดังตัวอย่างโปรแกรมข้างล่างนี้

โปรแกรมฝั่งเครื่องแม่ (Server):

/*
 ** dgram_server.c -- a datagram sockets "server"
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 4950 // the port users will be connecting to
#define MAXBUFLEN 100

int main(void) {
	int sockfd;
	struct sockaddr_in my_addr; // my address information
	struct sockaddr_in their_addr; // connector's address information
	socklen_t addr_len;
	int numbytes;
	char buf[MAXBUFLEN];

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}

	my_addr.sin_family = AF_INET; // host byte order
	my_addr.sin_port = htons(MYPORT); // short, network byte order
	my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
	memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct

	if (bind(sockfd, (struct sockaddr*) &my_addr, sizeof(struct sockaddr))
			== -1) {
		perror("bind");
		exit(1);
	}
	while (strcmp(buf, "close")) {
		addr_len = sizeof(struct sockaddr);
		if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN - 1, 0,
				(struct sockaddr*) &their_addr, &addr_len)) == -1) {
			perror("recvfrom");
			exit(1);
		}

		printf("got packet from %s\n", inet_ntoa(their_addr.sin_addr));
		printf("packet is %d bytes long\n", numbytes);
		buf[numbytes] = '\0';
		printf("packet from client contains \"%s\"\n", buf);
	}

	close(sockfd);

	return 0;
}

โปรแกรมฝั่งเครื่องลูก (Client):

/*
 ** dgram_client.c -- a datagram "client" 
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>


#define SERVERPORT 4950 // the port users will be connecting to
#define MAXBUFLEN 100


int main(int argc, char *argv[]) {
int sockfd;
struct sockaddr_in their_addr; // connector's address information
struct hostent *he;
int numbytes, i;
char buf[MAXBUFLEN];


if (argc != 2) {
fprintf(stderr, "usage: ./dgram_client <hostname>\n");
exit(1);
}


if ((he = gethostbyname(argv[1])) == NULL) {  // get the host info
perror("gethostbyname");
exit(1);
}


if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}


their_addr.sin_family = AF_INET; // host byte order
their_addr.sin_port = htons(SERVERPORT); // short, network byte order
their_addr.sin_addr = *((struct in_addr *) he->h_addr);
memset(&(their_addr.sin_zero), '\0', 8);  // zero the rest of the struct
while (strcmp(buf, "close")) {
fgets(buf, MAXBUFLEN, stdin);
for (i = 0; buf[i] != '\n'; i++)
;
buf[i] = '\0';
if ((numbytes = sendto(sockfd, buf, strlen(buf), 0,
(struct sockaddr *) &their_addr, sizeof(struct sockaddr)))
== -1) {
perror("sendto");
exit(1);
}


printf("sent %d bytes to %s\n", numbytes,
inet_ntoa(their_addr.sin_addr));
}
close(sockfd);


return 0;
}

ทำการคอมไพล์และรันโปรแกรมโดยแยกหน้าต่าง terminal ดังนี้

$ gcc -o dgram_server dgram_server.c
$ ./dgram_server
got packet from 127.0.0.1
packet is 20 bytes long
packet from client contains "Hello from Client..."

ตัวอย่างที่ 2

ตัวอย่างโปรแกรมเชื่อมต่อผ่านซ็อกเก็ตในลักษณะ connection-oriented (SOCK_STREAM) เมื่อทำการสถาปนาการเชื่อมต่อสำเร็จโดยเครื่องลูก (client) จะเชื่อมต่อไปยังเครื่องแม่ (server) แล้วทำการส่งข้อความไปยังเครื่องแม่ เมื่อเครื่องแม่ได้รับข้อความก็จะแสดงออกทางหน้าจอของดังตัวอย่างโปรแกรมข้างล่างนี้

โปรแกรมฝั่งเครื่องแม่ (Server):

/*
 ** strm_server.c -- a stream sockets "server"
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define TRUE 1

main() {
	int sock, length;
	struct sockaddr_in server;
	int msgsock;
	char buf[1024];
	int rval;
	int i;

	/* Create socket */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening stream socket");
		exit(1);
	}
	/* Name socket using wildcards */
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = 0;
	if (bind(sock, (struct sockaddr*) &server, sizeof(server))) {
		perror("binding stream socket");
		exit(1);
	}
	/* Find out assigned port number and print it out */
	length = sizeof(server);
	if (getsockname(sock, (struct sockaddr*) &server, &length)) {
		perror("getting socket name");
		exit(1);
	}
	printf("Socket has port #%d\n", ntohs(server.sin_port));

	/* Start accepting connections */
	listen(sock, 5);
	do {
		msgsock = accept(sock, 0, 0);
		if (msgsock == -1) {
			perror("accept");
			return EXIT_FAILURE;
		} else
			do {
				memset(buf, 0, sizeof(buf));
				if ((rval = read(msgsock, buf, 1024)) < 0)
					perror("reading stream message");
				else if (rval == 0)
					printf("Ending connection\n");
				else
					printf("-->%s\n", buf);
			} while (rval > 0);
		close(msgsock);
	} while (TRUE);
}

โปรแกรมฝั่งเครื่องลูก (Client):

/*
 ** strm_client.c -- a stream sockets "client"
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define DATA "Hello message from Client..."

main(int argc, char *argv[]) {
	int sock;
	struct sockaddr_in server;
	struct hostent *hp,* gethostbyname();

	/* Create socket */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening stream socket");
		exit(1);
	}
	/* Connect socket using name specified by command line.  */
	server.sin_family = AF_INET;
	hp = gethostbyname(argv[1]);
	if (hp == 0) {
		fprintf(stderr, "%s: unknown host\n", argv[1]);
		exit(2);
	}
	memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
	server.sin_port = htons(atoi(argv[2]));
	if (connect(sock, (struct sockaddr*) &server, sizeof(server)) < 0) {
		perror("connecting stream socket");
		exit(1);
	}
	if (write(sock, DATA, sizeof(DATA)) < 0)
		perror("writing on stream socket");
	close(sock);
}

ทำการคอมไพล์และรันโปรแกรมโดยแยกหน้าต่าง terminal ดังนี้

$ gcc -o strm_server strm_server.c
$ ./strm_server
Socket has port #37219
--> Hello message from Client...
Ending connection

จากการทำงานของโปรแกรมข้างต้น เมื่อโปรแกรม strm_server เริ่มทำงานบนเครื่องแม่แล้ว จะแจ้งหมายเลขพอร์ตที่เปิดรอให้เครื่องอื่นเข้ามาเชื่อมต่อ หลังจากนั้นเมื่อรันโปรแกรม strm_client ที่อยู่ในฝั่งของเครื่องลูกโดยระบุอาร์กิวเมนต์ตัวแรกเป็นหมายเลขที่อยู่ของเครื่องแม่ (127.0.0.1) และตัวที่สองเป็นหมายเลขพอร์ตที่ถูกเปิดในเครื่องแม่ (37219) จนกระทั่งการเชื่อมต่อระหว่างทั้งสองเครื่องถูกสถาปนาเรียบร้อยแล้ว ข้อความที่ถูกกำหนดไว้ในโปรแกรม strm_client ก็จะถูกส่งไปยังเครื่องแม่อัตโนมัติดังผลลัพธ์ข้างบน

ฟังก์ชัน accept() ภายในโปรแกรม strm_server จะถูกให้หยุดรอฟังการร้องขอการเชื่อมต่อจากเครื่องคอมพิวเตอร์ภายนอก ซึ่งถ้ายังไม่มีการร้องขอการเชื่อมต่อหรือข้อมูลที่อ่านได้มาจาก socket ยังมาไม่ครบ ณ เวลานั้นตัวโปรแกรม strm_server ก็ยังคงหยุดรอโดยไม่สามารถไปทำงานอย่างอื่นได้เลย

ดังนั้นเพื่อให้การทำงานของโปรแกรม strm_server กลายเป็นแบบ non-blocking กล่าวคือโปรแกรมยังสามารถทำงานอย่างอื่นได้อยู่ โดยที่ไม่ต้องหยุดรอเหมือนโปรแกรมข้างต้น ด้วยการเรียกใช้ฟังก์ชัน select() เพื่อทำการตรวจสอบว่าเมื่อใดที่มีการร้องขอการเชื่อมต่อ socket จากเครื่องภายนอก ก็จะค่อยไปเรียกฟังก์ชัน accept() ให้ทำงานทันที ด้วยวิธีการนี้เองสามารถประยุกต์ให้โปรแกรมบนเครื่องแม่ สามารถเปิดรับการเชื่อมต่อได้มากกว่าหนึ่ง socket ดังตัวอย่างโปรแกรม new_strm_server.c ที่ปรับปรุงใหม่ข้างล่างนี้

/*
 ** new_strm_server.c -- a stream sockets "server"
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define TRUE 1

/*
 * This program uses select() to check that someone
 * is trying to connect before calling accept().
 */

main() {
	int sock, length;
	struct sockaddr_in server;
	int msgsock;
	char buf[1024];
	int rval;
	fd_set ready;
	struct timeval to;

	/* Create socket */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening stream socket");
		exit(1);
	}
	/* Name socket using wildcards */
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = 0;
	if (bind(sock, (struct sockaddr*) &server, sizeof(server))) {
		perror("binding stream socket");
		exit(1);
	}
	/* Find out assigned port number and print it out */
	length = sizeof(server);
	if (getsockname(sock, (struct sockaddr*) &server, &length)) {
		perror("getting socket name");
		exit(1);
	}
	printf("Socket has port #%d\n", ntohs(server.sin_port));

	/* Start accepting connections */
	listen(sock, 5);
	do {
		FD_ZERO(&ready);
		FD_SET(sock, &ready);
		to.tv_sec = 5;
		if (select(sock + 1, &ready, 0, 0, &to) < 0) {
			perror("select");
			return EXIT_FAILURE;
		}
		if (FD_ISSET(sock, &ready)) {
			msgsock = accept(sock, (struct sockaddr*) 0, (int*) 0);
			if (msgsock == -1) {
				perror("accept");
				return EXIT_FAILURE;
			} else
				do {
					memset(buf, 0, sizeof(buf));
					if ((rval = read(msgsock, buf, sizeof(buf))) < 0)
						perror("reading    stream message");
					else if (rval == 0)
						printf("Ending connection\n");
					else
						printf("-->%s\n", buf);
				} while (rval > 0);
			close(msgsock);
		} else
			printf("Do something else...\n");
	} while (TRUE);
}

ทำการคอมไพล์และรันโปรแกรมอีกครั้งโดยแยกหน้าต่าง terminal ดังนี้

$ gcc -o new_strm_server new_strm_server.c
$ ./new_strm_server
Socket has port #45480
--> Hello message from Client...
Ending connection
Do something else
Do something else
Do something else

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University