Linux C Socket入门

(ZZ:http://hi.baidu.com/atlas1005/item/4dcbebf53af082de6325d2a2

socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。

具体解释详见http://baike.baidu.com/view/13870.htm

 

作为socket入门,首先建立最基本的局域网内两台计算机的连接和发送信息。

第一步:建立一个socket

socket编程的第一件事就是用socket()建立一个socket:

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;

 

 

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  {

    perror("socket 创建出错!");

    exit(1);

  }

第二步:名字绑定socket: bind() 

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

在这个函数里,sockfd是从socket()调用得到的文件描述句柄。name是一个指向sockaddr类型结构的一个指针。namelen给出了文件名的具体长度。

如果地址族被设定为AF_UNIX,这个类型的定义如下所示: 
struct sockaddr { 

u_short sa_family; 
char sa_data[14]; 
}; 
在这个结构种,name.sa_family应当被设定为AF_UNIX。name.sa_data应当包含最长为14个字节的文件名,这个文件名用来分配给socket。

#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 */ 
}; 

 

在这个结构种,name.sin_family应当被设定为AF_INET

#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() 

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

sockfd是我们建立的文件描述句柄,serv_addr是一个sockaddr结构,包含目的的地址和端口号,addrlen 被设定为sockaddr结构的大小。 

第四步:监听: listen() 

int listen(int sockfd, int backlog);

参数backlog是指一次可以监听多少个连接 
它的调用返回结果和上述的几个函数是一样的,这里就不多说了。值得一提的是,在这里,我们需要建立一个绑定,用来接收特定端口的服务请求。 

第五步:接受连接: accept()

当有人试图从我们打开的端口登陆进来时,我们应该响应他,这个时候就要用到accept()函数了。

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

第六步:输入和输入的完成: send() and recv() 

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() 

注意:如果使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实现数据传输。

最后一步: close() and shutdown() 

当传输结束时,应当关闭连接。

 #include <stdio.h> 

/* all you code */ 

close(sockfd); 

更保险的方法是用shutdown()来关闭连接。 

nt shutdown(int sockfd, int how) 
参数how的选择: 
0 - 不允许接收更多的数据 
1 - 不允许发送更多的数据 
2 - 不允许接收和发送更多的数据(和close()一样) 

以上就完成了socket的基本通信。

 

一些额外的语句:

你是谁: getpeerbyname() 
可能你还想知道是谁正在和你连接,那么看下面: 
#include <sys/socket.h> 
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 
参数addr是一个指向'struct sockaddr'或者'struct sockaddr_in'的指针。 
如果执行成功,我们就得到了对方的地址了,然后用inet_ntoa()用gethostbyad
dr()来得到对方更多的信息。

 

我是谁: gethostname() 
#include <unistd.h> 
int gethostname(char *hostname, size_t size); 
hostname是一个存放主机名字的字符数组 
返回的hostname可以作为gethostbyname()的参数,这样又可以得到自己的IP地址了。 

 

最后附上一个最简单的实例:

服务器端:

 

#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>

#include <netdb.h>

#include <arpa/inet.h>//for inet_ntoa

#include <unistd.h>//for fork()

#define SERVPORT 3333 //服务器监听端口号

#define BACKLOG 10 //最大同时连接请求数

int main()

{

  int sockfd, client_fd;//sockfd:监听socket;client_fd:数据传输socket

  struct sockaddr_in my_addr;//本机地址信息

  struct sockaddr_in remote_addr;//client address

  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 error");

    exit(1);

  }

  if (listen(sockfd, BACKLOG) == -1)

  {

    perror("listen error");

    exit(1);

  }

  while(1)

  {

    socklen_t sin_size = sizeof(struct sockaddr_in);//add socklen_t by zzz

    if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size))== -1)

    {

      perror("accept error");

      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 error");

      shutdown(client_fd, 2);

      exit(0);

    }

    shutdown(sockfd,2);

  }

  return 0;

}

 

客户端:

 

#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 //maxim data transfer

int 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 error!");

    exit(1);

  }

  if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) == -1)

  {

    perror("socket establishment error!");

    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 error!");

    exit(1);

  }

  if ((recvbytes = recv (sockfd, buf, MAXDATASIZE, 0)) == -1)

  {

    perror("recv error!");

    exit(1);

  }

  buf[recvbytes] = '\0';

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

  shutdown(sockfd,2);

  return 0;

}

posted @ 2012-12-03 15:18  菜鸟飞  阅读(360)  评论(0)    收藏  举报