linux的socket(bio編程)

linux的socket創建和java很像,其實,java不管是ServerSocket還是Socket,最終都要調到linux的socket函數。今天,我們會講linux中server與client的創建。

server

首先來看一個結構體sockaddr_in,它存儲了socket的地址:

    struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

AF_INET表示的是ipv4的網絡協議。

sin_port表示端口號。

in_addr表示IP主機地址。比如你有多個網卡,你究竟要監聽哪一個網卡呢?我們會使用INADDR_ANY,它表示0.0.0.0,也就是我們要綁定本機所有的IP地址並監聽它們。


我們來看socket函數:

 int socket(int domain, int type, int protocol);

The domain argument specifies a communication domain; this selects the protocol family which will be used for communication.

網絡協議家族我們選擇AF_INET

類型我們選擇SOCK_STREAM

The socket has the indicated type, which specifies the communication semantics. Currently defined types are:

SOCK_STREAM -----> Provides sequenced, reliable, two-way, connection-based byte streams.

SOCK_STREAM表示它是面向連接的可靠的字節流。

Sockets of type SOCK_STREAM are full-duplex byte streams, similar to pipes.
They do not preserve record boundaries. A stream socket must be in a connected state before any data may be sent or received on it.

最後一個參數是協議,它與協議家族domain有關:

The protocol specifies a particular protocol to be used with the socket.
Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0.

一般來說,一個協議家族中只存在一種協議,來支持特定的socket類型(SOCK_STREAM),這種情況下我們將protocol 這個參數設置爲0

關於返回值:

RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1
is returned, and errno is set appropriately.

成功就返回新建的socket的文件描述符,失敗就返回-1


socket有了,但它只是一個網絡協議層面的東西,它定義了ipv4的網絡協議,指定了可靠的字節流傳輸數據的形式。但是,它要作用在哪個主機上呢,它要監聽哪個端口呢?

這時,我們需要將socket綁定到sockaddr_in上。

我們看一下實現這個功能的bind函數:

int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

DESCRIPTION
When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to it. bind() assigns the address specified by addr to the socket referred to by the file descriptor sockfd.
addrlen specifies the size, in bytes, of the address structure pointed to by addr. Traditionally, this operation is called “assigning a name to a socket”.

It is normally necessary to assign a local address using bind() before a
SOCK_STREAM socket may receive connections (see accept(2)).

bind函數所需的三個參數我們都準備好了。我們有socket創建成功後返回的文件描述符,我們有socket具體地址信息的sockaddr_insocket的原義就是ip+port)。我們還能取到地址的大小。


接下來該做什麼呢?

To accept connections, the following steps are performed:

  1. A socket is created with socket(2).
  2. The socket is bound to a local address using bind(2), so that other sockets may be connect(2)ed to it.
  3. A willingness to accept incoming connections and a queue limit for incoming connections are specified with listen().
  4. Connections are accepted with accept(2).

下面我們要使用listen來去主動監聽連接。

 int listen(int sockfd, int backlog);

socket文件描述符我們有,但backlog是什麼呢?

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.

它就是等待連接的一個隊列的長度,客戶端每來連接一次,都會被放到這個隊列中,然後等待accept


從上面的4個步驟我們知道,最後一步是accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd我們已經準備好了:

The argument sockfd is a socket that has been created with socket(2), bound to a local address with bind(2), and is listening for connections after a listen(2).

The argument addr is a pointer to a sockaddr structure. This structure is filled in with the address of the peer socket, as known to the communications layer.

同時我們需要客戶端的地址信息。


最後我們需要receive數據:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

前三個參數好理解,最後一個參數一般爲0,就是不需要特別的flags

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8000
int main(int argc, char *argv[]) {
	/*variables*/
	int sockfd;
	struct sockaddr_in serveraddr, clientaddr;
	int mysock;
	int addrlen;
	char buff[1024];
	int rval;

	/*create socket*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		perror("Failed to create socket");
		exit(1);
	}
	//ipv4 family
	serveraddr.sin_family = AF_INET;
	//ip
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(SERVER_PORT);

	/*call bind*/
	if (bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr))) {
		perror("bind failed");
		exit(1);
	}

	/*listen*/
	listen(sockfd, 5);

	addrlen = sizeof(clientaddr);

	/*accept*/
do {
	mysock = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen);
	if(mysock == -1) {
		perror("accept failed");
	} else {
		memset(buff,0,sizeof(buff));
		if((rval = recv(mysock,buff,sizeof(buff),0))<0) {
			perror("reading stream message error");
		} else if(rval==0) {
			printf("Ending connection\n");
		} else {
			printf("message: %s\n",buff);
		}
		printf("got the message rval=%d\n",rval);
		close(mysock);
	}
}while(1);

return 0;
}

注意0.0.0.08000是字符串,我們要將其轉換成網絡字節序。

因此涉及到htonl()htons()兩個函數。

The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.


client

客戶端要連接server,要有socket,要知道server的ip和端口號,要能夠發送數據。

這裏還要介紹一個函數:
int inet_pton(int af, const char *src, void *dst);

它將字符串轉換成二進制的網絡地址。

我們要連接server,一定要寫明server的ip地址,但這是個字符串,所以要有這樣的轉換。

This function converts the character string src into a network address structure in the af address family, then copies the network address structure to dst. The af argument must
be either AF_INET or AF_INET6.

我們要把ip字符串(比如192.168.8.111)轉換成AF_INET協議族中的網絡結構地址,並且存儲在dst這個destination中。

client的代碼如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#define SERVER_PORT 8000
#define DATA "HELLO WORLD"
int main(int argc, char *argv[]){
	/*variables*/
	int sockfd;
	struct sockaddr_in serveraddr;
	char ipstr[] = "192.168.8.111";

	/*create socket*/
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0){
		perror("socket failed");
		close(sockfd);
		exit(1);
	}

	/*initialize server address*/
	serveraddr.sin_family = AF_INET;

	inet_pton(AF_INET, ipstr, &serveraddr.sin_addr.s_addr);
	serveraddr.sin_port = htons(SERVER_PORT);

	/*connect*/
	if(connect(sockfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
		perror("connect failed");
		close(sockfd);
		exit(1);
	}

	/*send data*/
	if(send(sockfd, DATA, sizeof(DATA), 0) < 0){
		perror("send failed");
		close(sockfd);
		exit(1);
	}
	printf("sent %s\n", DATA);
	close(sockfd);

}


gcc編譯後,我們阻塞的socket的server和client就能運行了,並且能夠發送數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章