原文链接:http://gstarwd.javaeye.com/blog/539245
关键字: linux-socket c实现
socket()
我们使用系统调用 socket() 来获得文件描述符:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
第一个参数 domain 设置为 “AF_INET” 。
第二个参数是套接口的类型: SOCK_STREAM 或
SOCK_DGRAM 。第三个参数设置为 0 。
系统调用 socket() 只返回一个套接口描述符,如果出错,则返回 -1 。
bind()
一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。但如果你只想使用 connect() 则无此必要。
下面是系统调用 bind() 的使用方法:
#include<sys/types.h>
#include<sys/socket.h>
intbind(int sockfd,struct sockaddr*my_addr,int addrlen);
第一个参数 sockfd 是由 socket() 调用返回的套接口文件描述符。
第二个参数 my_addr 是指向数据结构 sockaddr 的指针。数据结构 sockaddr 中包括了关于你的地址、端口和 IP 地址的信息。
第三个参数 addrlen 可以设置成 sizeof(structsockaddr) 。
下面是一个例子:
- #include<string.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #defineMYPORT3490
- main()
- {
- int sockfd;
- struct sockaddr_inmy_addr;
- sockfd=socket(AF_INET,SOCK_STREAM,0);/*do someerror checking!*/
- my_addr.sin_family=AF_INET;/*hostbyteorder*/
- my_addr.sin_port=htons(MYPORT);/*short,network byte order*/
- my_addr.sin_addr.s_addr=inet_addr("132.241.5.10");
- bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/
- /*don't forget your error checking for bind():*/
- bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr));
- ...
如果出错, bind() 也返回 -1 。
如果你使用 connect() 系统调用,那么你不必知道你使用的端口号。当你调用 connect() 时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。
connect()
系统调用 connect() 的用法如下:
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);
第一个参数还是套接口文件描述符,它是由系统调用 socket() 返回的。
第二个参数是 serv_addr 是指向数据结构 sockaddr 的指针,其中包括目的端口和 IP 地址。
第三个参数可以使用 sizeof(structsockaddr) 而获得。
下面是一个例子:
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#defineDEST_IP"132.241.5.10"
#defineDEST_PORT23
main()
{
intsockfd;
structsockaddr_indest_addr;/*will hold the destination addr*/
sockfd=socket(AF_INET,SOCK_STREAM,0);/*do some error checking!*/
dest_addr.sin_family=AF_INET;/*hostbyteorder*/
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*/
/*don'tforgettoerrorchecktheconnect()!*/
connect(sockfd,(structsockaddr*)&dest_addr,sizeof(struct sockaddr));
...
同样,如果出错, connect() 将会返回 -1 。
listen()
如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用 listen() ,然后再调用 accept() 来实现。
系统调用 listen() 的形式如下:
intl isten(int sockfd,int backlog);
第一个参数是系统调用 socket() 返回的套接口文件描述符。
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用 accept() 应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为 20 。你可以设置为 5 或者 10 。当出错时, listen() 将会返回 -1 值。
当然,在使用系统调用 listen() 之前,我们需要调用 bind() 绑定到需要的端口,否则系统内核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统调用的顺序:
socket();
bind();
listen();
/*accept()goeshere*/
accept()
系统调用 accept() 比较起来有点复杂。在远程的主机可能试图使用 connect() 连接你使用
listen() 正在监听的端口。但此连接将会在队列中等待,直到使用 accept() 处理它。调用 accept()
之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接
来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描
述符可以用来调用 send() 和 recv() 。
调用的例子如下:
#include<sys/socket.h>
intaccept(intsockfd,void*addr,int*addrlen);
第一个参数是正在监听端口的套接口文件描述符。第二个参数 addr 是指向本地的数据结构
sockaddr_in 的指针。调用 connect() 中的信息将存储在这里。通过它你可以了解哪个主机在哪个
端口呼叫你。第三个参数同样可以使用 sizeof(structsockaddr_in) 来获得。
如果出错, accept() 也将返回 -1 。下面是一个简单的例子:
- #include<string.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #defineMYPORT3490/*theportuserswillbeconnectingto*/
- #defineBACKLOG10/*howmanypendingconnectionsqueuewillhold*/
- main()
- {
- intsockfd,new_fd;/*listenonsock_fd,newconnectiononnew_fd*/
- structsockaddr_inmy_addr;/*myaddressinformation*/
- structsockaddr_intheir_addr;/*connector'saddressinformation*/
- intsin_size;
- sockfd=socket(AF_INET,SOCK_STREAM,0);/*dosomeerrorchecking!*/
- my_addr.sin_family=AF_INET;/*hostbyteorder*/
- my_addr.sin_port=htons(MYPORT);/*short,networkbyteorder*/
- my_addr.sin_addr.s_addr=INADDR_ANY;/*auto-fillwithmyIP*/
- bzero(&(my_addr.sin_zero),8);/*zerotherestofthestruct*/
- /*don'tforgetyourerrorcheckingforthesecalls:*/
- bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr));
- listen(sockfd,BACKLOG);
- sin_size=sizeof(structsockaddr_in);
- new_fd=accept(sockfd,&their_addr,&sin_size);
- ...
下面,我们将可以使用新创建的套接口文件描述符 new_fd 来调用 send() 和 recv() 。
send() 和 recv()
系统调用 send() 的用法如下:
int send(int sockfd,const void* msg,int len,int flags);
第一个参数是你希望给发送数据的套接口文件描述符。它可以是你通过 socket() 系统调用返回的,也可以是通过 accept() 系统调用得到的。
第二个参数是指向你希望发送的数据的指针。
第三个参数是数据的字节长度。第四个参数标志设置为 0 。
下面是一个简单的例子:
char*msg="Beejwashere!";
intlen,bytes_sent;
..
len=strlen(msg);
bytes_sent=send(sockfd,msg,len,0);
...
系统调用 send() 返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回的字节数比要发送的字节数少,你在以后必须发送剩下的数据。当 send() 出错时,将返回 -1 。
系统调用 recv() 的使用方法和 send() 类似:
int recv(int sockfd,void* buf,int len,unsigned int flags);
第一个参数是要读取的套接口文件描述符。
第二个参数是保存读入信息的地址。
第三个参数是缓冲区的最大长度。第四个参数设置为 0 。
系统调用 recv() 返回实际读取到缓冲区的字节数,如果出错则返回 -1 。
这样使用上面的系统调用,你可以通过数据流套接口来发送和接受信息。
sendto() 和 recvfrom()
因为数据报套接口并不连接到远程的主机上,所以在发送数据包之前,我们必须首先给出目的地址,请看:
int sendto(int sockfd,const void* msg,int len,unsigned int flags,
conststruct sockaddr*to,inttolen);
除了两个参数以外,其他的参数和系统调用 send() 时相同。
参数 to 是指向包含目的 IP 地址和端口号的数据结构 sockaddr 的指针。
参数 tolen 可以设置为 sizeof(structsockaddr) 。
系统调用 sendto() 返回实际发送的字节数,如果出错则返回 -1 。
系统调用 recvfrom() 的使用方法也和 recv() 的十分近似:
int recvfrom(int sockfd,void* buf,int len,unsigned int flags
struct sockaddr* from,int* fromlen);
参数 from 是指向本地计算机中包含源 IP 地址和端口号的数据结构 sockaddr 的指针。
参数 fromlen 设置为 sizeof(struct sockaddr) 。
系统调用 recvfrom() 返回接收到的字节数,如果出错则返回 -1 。
close() 和 shutdown()
你可以使用 close() 调用关闭连接的套接口文件描述符:
close(sockfd);
这样就不能再对此套接口做任何的读写操作了。
使用系统调用 shutdown() ,可有更多的控制权。它允许你在某一个方向切断通信,或者切断双方的通信:
int shutdown(int sockfd,int how);
第一个参数是你希望切断通信的套接口文件描述符。第二个参数 how 值如下:
0—Furtherreceivesaredisallowed
1—Furthersendsaredisallowed
2—Furthersendsandreceivesaredisallowed(likeclose())
shutdown() 如果成功则返回 0 ,如果失败则返回 -1 。
getpeername()
这个系统的调用十分简单。它将告诉你是谁在连接的另一端:
#include<sys/socket.h>
int getpeername(int sockfd,struct sockaddr* addr,int* addrlen);
第一个参数是连接的数据流套接口文件描述符。
第二个参数是指向包含另一端的信息的数据结构 sockaddr 的指针。
第三个参数可以设置为 sizeof(structsockaddr) 。
如果出错,系统调用将返回 -1 。
一旦你获得了它们的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 来得到更多的信息。
gethostname()
系统调用 gethostname() 比系统调用 getpeername() 还简单。它返回程序正在运行的计算机的名字。系统调用 gethostbyname() 可以使用这个名字来决定你的机器的 IP 地址。
下面是一个例子:
#include<unistd.h>
int gethostname(char*hostname,size_tsize);
如果成功, gethostname 将返回 0 。如果失败,它将返回 -1 。
SOCKET C 程序代码
Makefile 文件
s: app_service.c
gcc -o s app_service.c
c: app_client.c
gcc -o c app_client.c
app_client.c 文件
- //客户端程序代码如下:
- #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 SERVPORT 3333
- #define MAXDATASIZE 100 // 每次最大数据传输量
- main(int argc, char *argv[])
- {
- int sockfd, recvbytes;
- char buf[MAXDATASIZE];
- struct hostent *host;
- struct sockaddr_in serv_addr;
- if (argc < 2) {
- fprintf(stderr,"Please enter the server's hostname!/n");
- exit(1);
- }
- if ((host = gethostbyname(argv[1])) == NULL) {
- herror("gethostbyname出错!");
- exit(1);
- }
- if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
- perror("socket创建出错!");
- exit(1);
- }
- serv_addr.sin_family = AF_INET;
- serv_addr.sin_port = htons(SERVPORT);
- serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
- bzero(&(serv_addr.sin_zero), 8);
- if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {
- perror("connect出错!");
- exit(1);
- }
- if ((recvbytes = recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) {
- perror("recv出错!");
- exit(1);
- }
- buf[recvbytes] = '/0';
- printf("Received: %s",buf);
- close(sockfd);
- }
- /*
- 客户端程序首先通过服务器域名获得服务器的IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket。
- 函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数原型为:
- struct hostent *gethostbyname(const char *name);
- 函数返回为hosten的结构类型,它的定义如下:
- struct hostent {
- char *h_name; // 主机的官方域名
- char **h_aliases; // 一个以NULL结尾的主机别名数组
- int h_addrtype; // 返回的地址类型,在Internet环境下为AF-INET
- int h_length; // 地址的字节长度
- char **h_addr_list; // 一个以0结尾的数组,包含该主机的所有地址
- };
- #define h_addr h_addr_list[0] //在h-addr-list中的第一个地址
- 当 gethostname()调用成功时,返回指向struct hosten的指针,当调用失败时返回-1。当调用gethostbyname时,你不能使用perror()函数来输出错误信息,
- 应该使用herror()函数来输出。
- 无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的,两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接,而且在发送接收
- 数据时,需要指定远端机的地址。
- */
app_service.c 文件
- /*
- 面向连接的Socket实例
- 代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件,在客户端运行客户软件,客户端就会收到该字符串。
- 该服务器软件代码如下:
- */
- #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 SERVPORT 3333 // 服务器监听端口号
- #define BACKLOG 10 // 最大同时连接请求数
- main()
- {
- int sockfd, client_fd; // sock_fd:监听socket;client_fd:数据传输socket
- struct sockaddr_in my_addr; // 本机地址信息
- struct sockaddr_in remote_addr; // 客户端地址信息
- int sin_size;
- if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
- perror("socket创建出错!"); exit(1);
- }
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(SERVPORT);
- my_addr.sin_addr.s_addr = INADDR_ANY;
- bzero(&(my_addr.sin_zero), 8);
- if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
- perror("bind出错!");
- exit(1);
- }
- if (listen(sockfd, BACKLOG) == -1) {
- perror("listen出错!");
- exit(1);
- }
- while(1) {
- sin_size = sizeof(struct sockaddr_in);
- if ((client_fd = accept(sockfd, (void *)&remote_addr, &sin_size)) == -1) {
- perror("accept出错");
- continue;
- }
- printf("received a connection from %s/n", inet_ntoa(remote_addr.sin_addr));
- if (!fork()) { /* 子进程代码段 */
- if (send(client_fd, "Hello, you are connected!/n", 26, 0) == -1) {
- perror("send出错!");
- close(client_fd);
- exit(0);
- }
- }
- close(client_fd);
- }
- }
- /*
- 服务器的工作流程是这样的:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用 listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串"Hello,you are connected!"。最后关闭该socket。
- 代码实例中的fork()函数生成一个子进程来处理数据传输部分,fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分,它与if语句后面的父进程代码部分是并发执行的。
- */