Socket Programming

BSD Sockets

ประเภทการกลไกสื่อสาร IPC

เริ่มต้นตั้งแต่ระบบปฏิบัติการ 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

แสดงการเชื่อมต่อระดับโปรแกรมประยุกต์และหมายเลขพอร์ตของ TCP/IP

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

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

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

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

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

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

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

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

แสดงขบวนการเชื่อมต่อระหว่างเครื่องภายในระบบเครือข่ายผ่าน Socket

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

  • ไลบรารีที่เกี่ยวข้องคือ 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

Was this helpful?