用C語言編寫Socket程式

來源:互聯網
上載者:User

 本文的目的在於為初學者提供一個快速的入門指導,用來迅速熟悉用C語言來編寫
Internet 網路應用程式。本文假設讀者已經具備了C語言的基本知識和文法,並且讀者有使用Uinx/Linux的經驗。儘管Uinx/Linux的Socket編程 與在Windows下的有一些不同的地方,但是在此我並不想展開。另外,本文所有的程式都在Red Hat 5.2下編譯通過,並且在 glibc 2.0.7和libc 5.3.12兩種環境下都沒有問題。現在就開始我們的教程吧:)。

對一個程式員而言,sockets和底層的檔案描述符非常類似(可以在sockets裡使用read()和write()函數),儘管建立一個socket比開啟,讀取和寫入一個檔案更為麻煩,但這是由於網路連接比單純的本地硬碟的讀寫複雜的多所造成的。

通常,sockets用來實現客戶機/伺服器對。伺服器的任務是監聽某個特定的連接埠,當接收到用戶端的服務要求時完成相應的服務;客戶機的任務是請求伺服器完成事先設定好的服務。

作為入門級的文章,我們在這裡不會使用所有的socket類型和功能,但是我們會向讀者提供足夠的資訊。現在,就讓我們開始吧。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
建立一個socket:socket()

你所要學的socket編程的第一件事就是用socket()建立一個socket:

- -------

#include <sys/types.h>
#include <sys/socket.h>

int socket(int af, int type, int protocol)

- ------

'int af'代表地址族或者稱為socket所代表的域,通常有兩個選項:
     AF_UNIX - 只在單機上使用。
     AF_INET - 可以在單機或其他使用DARPA協議(UDP/TCP/IP)的異種機通訊。

'int type'代表你所使用的連線類型,通常也有兩種情況:
     SOCK_STREAM - 用來建立連線導向的sockets,可以進行可靠無誤的的資料傳

     SOCK_DGRAM - 用來建立沒有串連的sockets,不能保證資料轉送的可靠性。

在本文中,我們著重使用AF_INET地址族和SOCK_STREAM連線類型。

'int protocol'通常設定為0。這樣的目的是使系統選擇預設的由協議族和連線類型所確定的協議。

這個函數的傳回值是一個檔案描述控制代碼,如果在此期間發生錯誤則返回-1並且設定了相應的errno。

- ------

#include <sys/types.h>
#include <sys/socket.h>

int sockfd /* soon to be socket file descriptor */

sockfd = socket(AF_INET, SOCK_STREAM, 0)
/* error checking here */

- ------

如果執行成功,我們就擁有了一個socket的檔案控制代碼,通過這個控制代碼就可以訪問
Internet了。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
名字綁定socket: bind()

下一步要完成的就是名字綁定工作了:

- ------

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *name, int namelen)

- ------

在這個函數裡,sockfd是從socket()調用得到的檔案描述控制代碼。name是一個指向
sockaddr類型結構的一個指標。如果地址族被設定為AF_UNIX,這個類型的定義是
如下所示:

- ------

struct sockaddr {
u_short sa_family;
char sa_data[14];
};

- ------

在這個結構種,name.sa_family應當被設定為AF_UNIX。name.sa_data應當包含最
長為14個位元組的檔案名稱,這個檔案名稱用來分配給socket。namelen給出了檔案名稱的
具體長度。

- ------

#include <sys/types.h>
#include <sys.socket.h>

struct sockaddr name;
int sockfd;

name.sa_family = AF_UNIX;
strcpy(name.sa_data, "/tmp/whatever");

sockfd = socket(AF_UNIX, SOCK_STREAM, 0)
/* error checking code here */

bind(sockfd, &name, strlen(name.sa_data) + sizeof(name.sa_family)
/* error checking code here */

- ------

如果調用成功,則傳回值為0,如果調用失敗傳回值為-1,並設定相應的錯誤碼errno。

現在,讓我們在使用另一種結構,它是在使用AF_INET地址族的時候使用的。

- -----

struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};

- ------

看起來它比前一個大多了,但要掌握它並不十分困難。

- ------

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>

int sockfd, port = 23;
struct sockaddr_in my_addr;

if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("Socket Error, %d\n", errno);
exit(1);
}

my_addr.sin_family = AF_INET; /* host byte order */
my_addr.sin_port = htons(port); /* see man htons for more information
*/
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* get our address */
bzero(&(my_addr.sin_zero), 8); /* zero out the rest of the space */

if((bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))
== -1)
{
printf("Bind Error, %d\n", errno);
close(sockfd);
exit(1);
}

- ------

現 在,如果沒有問題的話,我們建立的socket就有一個名字了!相反,如果不成功,它會設定相應的錯誤碼,並使程式退出。這裡需要說明的是,如果你的計 算機不想和別人的電腦串連,那麼完全沒有必要使用bind()。對於連接埠的綁定,在伺服器而言是不合適的,它只應該在客戶機上實現。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
遠端連線: connect()

這是和別的電腦串連所必須的一步。

- ------

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);  

- ------

sockfd是我們建立的檔案描述控制代碼,serv_addr是一個sockaddr結構,包含目的的
地址和連接埠號碼,addrlen 被設定為sockaddr結構的大小。

- ------

#include <string.h>  
#include <sys/types.h>  
#include <sys/socket.h>  

#define DEST_IP "132.241.5.10"
#define DEST_PORT 23

main()
{
int sockfd;
struct sockaddr_in dest_addr; /* will hold the destination addr */

sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! *
/

dest_addr.sin_family = AF_INET; /* host byte order */
dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */

dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
bzero(&(dest_addr.sin_zero), 8); /* zero the rest of the struct */

connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)
);
/* error checking code here */
/* more code  
.
.
.
*/
}

- ------

同樣,connect()在調用返回後,如果傳回值為0則表明成功,如果是1則說明有錯誤,並且同時設定了相應的錯誤碼。由於我們現在不關心具體和那個連接埠串連,所以在上面的常式裡我們沒有調用了bind()函數。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
監聽: listen()

當我們需要建立一個伺服器的時候,我們需要有一種手段來監聽輸入的請求,而
listen()函數正是提供這個功能。

- ------

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

- ------

參數backlog是指一次可以監聽多少個串連

它的調用返回結果和上述的幾個函數是一樣的,這裡就不多說了。
值得一提的是,在這裡,我們需要建立一個綁定,用來接收特定連接埠的服務要求。

- ------

socket(); /* to create out socket file descriptor */
bind(); /* to give our socket a name */
listen(); /* listen for connection */

- ------

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
接受串連: accept()

好了,現在開始實質性的工作了。當有人試圖從我們開啟的連接埠登陸進來時我們應該響應他,這個時候就要用到accept()函數了。

- ------

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, void *addr, int *addrlen);

- ------

函數調用所需的參數都是我們所熟悉的:)

- ------

#include <string.h>  
#include <sys/types.h>  
#include <sys/socket.h>  

#define MYPORT 1500 /* the port users will be connecting to */
#define BACKLOG 5 /* how many pending connections queue will hold */

main()
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */

struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int sin_size;

sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! *
/

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; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */

/* did you remember your error checking? */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

listen(sockfd, BACKLOG);

sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, &their_addr, &sin_size);

- ------

這裡我們要注意的是:我們用new_fd來完成所有的接收和發送的操作。如果在只有一個串連的情況下你可以關閉原來的sockfd用來防止更多的輸入請求。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
輸入和輸入的完成: send() and recv()

在我們完成了上述的工作後,最後一步就是傳輸資料了:)。在這裡,我們通過send()和recv()來實現。

- ------

#include <sys/types.h>
#include <sys/socket.h>

int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd, void *buf, int len, unsigned int flags);

- ------
send():
sockfd - socket file descriptor
msg - message to send
len - size of message to send
flags - read 'man send' for more info, set it to 0 for now

recv():
sockfd - socket file descriptor
buf - data to receive
len - size of buf
flags - same as flags in send()

send() 常式:
- ------

char *msg = "Hey there people";
int len, send_msg;

/* code to create(), bind(), listen() and accept() */  

len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);

- ------
recv() 常式:
- ------

char *buf;
int len, recv_msg;

/* code to create(), bind(), listen() and accept() */

len = strlen(buf);
recv_msg = recv(sockfd, buf, len, 0);

- ------

如果你使用的連線類型是SOCK_DGRAM,那麼應該使用sendto()和recvfrom()來實現資料轉送。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
最後一步: close() and shutdown()

當傳輸結束時,應當關閉串連。

- ------

#include <stdio.h>

/* all you code */

close(sockfd);

- ------

更保險的方法是用shutdown()來關閉串連。

- ------

int shutdown(int sockfd, int how)

- ------

參數how的選擇:
1 - 不允許接收更多的資料
2 - 不允許發送更多的資料
3 - 不允許接收和發送更多的資料(和close()一樣)

一切就是這麼簡單:)

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
你是誰: getpeerbyname()

可能你還想知道是誰正在和你串連,那麼看下面:

- ------

#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

- ------

參數addr是一個指向'struct sockaddr'或者'struct sockaddr_in'的指標。

如果執行成功,我們就得到了對方的地址了,然後用inet_ntoa()用gethostbyad
dr()來得到對方更多的資訊。如果還想知道更多的,請參閱RFC 1413。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
我是誰: gethostname()

- ------

#include <unistd.h>

int gethostname(char *hostname, size_t size);

- ------
hostname是一個存放主機名稱字的字元數組

返回的hostname可以作為gethostbyname()的參數,這樣又可以得到自己的IP地址了。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
我的IP是多少?  

現在把我們所學的集中起來,逐步就可以完成一個實用的程式了。還是來研究一下如下的情況:

$ telnet microsoft.com
Trying 206.163.24.176 (not the real address but I'm too lazy to try
)

我們看到,telnet程式所做的第一件事情是網域名稱解析!這個工作是由gethostbyname()完成的。

- ------

#include <netdb.h>  

struct hostent *gethostbyname(const char *name);

- ------

一個特殊的結構hostent:

- ------

struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0]

- ------
這個結構可以被分為:

h_name - 正式的機器名
h_aliases - 一個以NULL結尾的字串,表示機器的別名。  
h_addrtype - 地址所使用的類型,通常是AF_INET。  
h_length - 用位元組數表示的地址長度。
h_addr_list - 一個以0結尾的數組,表示機器的網路地址,以網路的位元組順序排列。  
h_addr - 在h_addr_list裡的第一個地址。

gethostbyname()返回一個指向hostent結構的指標或者是一個NULL指標表示錯誤(儘管如此,錯誤碼沒有設定!)。

現在我們就來完成我們的DNS程式:

- ------

#include <stdio.h>  
#include <stdlib.h>  
#include <errno.h>  
#include <netdb.h>  
#include <sys/types.h>
#include <netinet/in.h>  

int main(int argc, char *argv[])
{
struct hostent *h;

if (argc != 2) { /* error checking on the command line */
fprintf(stderr,"Usage: getip <host name>\n");
exit(1);
}

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

printf("Host name : %s\n", h->h_name);
printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));

return 0;
}

- ------

用gcc -o getip getip.c就可以了完成了。

=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==
+==+==+=
客戶機和伺服器程式

好了,讓我們用一個小的客戶機-伺服器應用來結束本文的討論。

這個小程式的目的就是讓一個使用者和伺服器串連,接收預先定義好的資料,然後斷開。不過這個程式中有很多錯誤需要你來修改,作為你的作業來完成:)。

- ------

<++> socket/server.c
/* SERVER PROGRAM */
#include <stdio.h>  
#include <stdlib.h>  
#include <errno.h>  
#include <string.h>  
#include <sys/types.h>  
#include <netinet/in.h>  
#include <sys/socket.h>  
#include <sys/wait.h>  

#define PORT 1500 /* the port users will be connecting to */

#define BACKLOG 5 /* how many pending connections queue will hold */

main()
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */

struct sockaddr_in my_addr; /* our address information */
struct sockaddr_in their_addr; /* their address information */
int sin_size;

sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* remember to error check (-1 on error) */

my_addr.sin_family = AF_INET; /* host byte order */
my_addr.sin_port = htons(PORT); /* short, network byte order */
my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */

bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

listen(sockfd, BACKLOG)

while(1) { /* start out accept() loop */
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)
printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_ad
dr));
fork(); /* this is the child process */
send(new_fd, "Hello, world!\n", 14, 0)
close(new_fd);
exit(0);

while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
}
}

/* END SERVER PROGRAM, REMEMBER TO DO YOUR ERROR CHECKING */
<-->
<++> socket/client.c
/* CLIENT PROGRAM */

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

#define PORT 1500 /* the port client will be connecting to */

#define MAXDATASIZE 100 /* max number of bytes we can get at once */

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

if (argc != 2) {
fprintf(stderr,"Usage: client <host name>\n");
exit(1);
}

he = gethostbyname(argv[1]); /* get the host info */
/* did you check for errors? */

sockfd = socket(AF_INET, SOCK_STREAM, 0);

their_addr.sin_family = AF_INET; /* host byte order */
their_addr.sin_port = htons(PORT); /* short, network byte order */
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */

connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr
));

numbytes = recv(sockfd, buf, MAXDATASIZE, 0);

buf[numbytes] = '\0';

printf("Received: %s",buf);

close(sockfd);

return 0;
}

/* END CLIENT...YOU CHECKED FOR ERRORS RIGHT? */
<-->

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.