网络编程浅析(附加源码)

文章开始前先转载两篇文章:

第一篇是来自www.ChinaUnix.com

Linux网络协议源代码分析 之 入门
来源: ChinaUnix博客  日期: 2010.01.18 18:15 (共有条评论) 我要评论
 
普通意义上的socket编程是基于GNU提供的库函数之上实现的,比创建一个socket时候调用的socket()函数就是在 sys/socket.h中定义的,此文件在Linux系统下的路径为: /usr/include/sys。 GNU提供的标准接口,可以用来创建socket,然后开始利用此socket通信。具体的接口如下所示:
1. 创建套接字接口socket函数。函数原型为:

int socket(int domain, int type, int protocol)  
这个函数有三个参数,第一个参数指定协议族,如AF_INET(IPv4协议),AF_INET6 (IPv6协议),AF_LOCAL(Unix域协议)。第二个参数为套接字类型。第三个参数指定协议,也可以取0。Socket函数成功时返回一个套接字文件描述符。
2. 绑定套接字接口 bind函数。函数原型为:

int bind(int sockfd, struct sockaddr *my_addr, int addrlen)   
其中各项参数的含义为:
sockfd:由socket调用返回的文件描述符
addrlen:就是sizeof(sockaddr), 套接字地址结构的长度
my_addr:一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;sockaddr的定义如下:

struct sockaddr{
  unsigned short sa_family; /* 地址族, AF_xxx */
   char sa_data[14]; /* 14 字节的协议地址 */   
};  
另外还有一种用于Internet的套接字地址结构类型:

struct sockaddr_in {
   short int sin_family; /* 地址族 */
   unsigned short int sin_port; /* 端口号 */
   struct in_addr sin_addr; /* IP地址 */
   unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */
};  
bind函数返回值表明操作成功或失败:成功返回0,出错返回-1。
3. 建立连接接口connect函数
面向连接的客户端程序使用connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:

int connect(int sockfd, struct sockaddr *serv_addr,int addrlen)  
其中sockfd是socket函数返回的socket描述符;serv_addr是包含远端主机IP地址和端口号的指针;addrlen是远端地址结构的长度。connect函数在出现错误时返回-1,并且设置errno为相应的错误码。编写客户端程序无须调用bind()。
4. 监听接口 listen函数
listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。

int listen(int sockfd, int backlog)  
5. 接受请求接口accept函数
accept函数由TCP服务器端调用,用来接受从客户端来的请求。如果没有请求,则该函数自行阻塞,直到有请求为止。

int accept(int sockfd, struct sockaddr *cliaddr,socklen_t *addrlen)  
6. 关闭套接字接口close函数

int close( int sockfd)  
close对TCP套接字sockfd的默认的操作是将其标识为已关闭并立即返回;这时套接字描述符就不能再被进程使用了,也不能作为read和write的参数了。
    Linux 下socket网络编程是非常简单的一件事情,复杂的是其背后如何实现的,以及如何提交给Linux内核来完成相应的工作的。比如socket()函数返回的是一个整形变量,它是一个套接字的描述符,系统是如何将一个整形变量管理为套接字的描述符呢?再比send()函数是如何将用户申请的一块内存中的内容发给内核,而内核又经过了哪些处理转发出去的呢?这就需要深入Linux网络编程。
   顺藤摸瓜,先看GNU对socket的处理。那么GNU又是如何将一个socket提交给Linux内核处理的呢?这个过程就是用户空间到内核空间的提交。
   要知道,要深入Linux是如何在操作系统一级实现宏大的TCP/IP协议栈的,请大家跟我来慢慢分析。


补充:

1.对于bind 函数要说明的一点是,你可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号: 
  my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */ 
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */ 
 
 通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将
my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。Bind()函数在成功被调用时返回0;遇到错
误时返回"-1"并将errno置为相应的错误号。另外要注意的是,当调用函数时,一般不要将端口号置为小于1024的值,因为1~1024是保留端口号,你可以使用大于1024中任何一个没有被占用的端口号。


第二篇是www.SUDU.CN摘录的一篇文章,写得不错

计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节优先顺
序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需
要进行转换。
  我们要讨论的第一个结构类型是:struct sockaddr,该类型是用来保存socket信息的: 
  struct sockaddr { 
        unsigned short sa_family; /* 地址族, AF_xxx */ 
         char sa_data[14]; /* 14 字节的协议地址 */
          }; 
 sa_family一般为AF_INET;sa_data则包含该socket的IP地址和端口号。 
另外还有一种结构类型: 
  struct sockaddr_in { 
        short int sin_family; /* 地址族 */ 
        unsigned short int sin_port; /* 端口号 */ 
        struct in_addr sin_addr; /* IP地址 */ 
        unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */ 
        }; 
 
这个结构使用更为方便。sin_zero(它用来将sockaddr_in结构填充到与struct
sockaddr同样的长度)应该用bzero()或memset()函数将其置为零。指向sockaddr_in
的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向
sockaddr_in的指针转换为指向sockaddr的指针;或者相反。sin_family通常被赋AF_INET;sin_port和
sin_addr应该转换成为网络字节优先顺序;而sin_addr则不需要转换。 
 我们下面讨论几个字节顺序转换函数: 
  htons()--"Host to Network Short" ; htonl()--"Host to Network Long" 
  ntohs()--"Network to Host Short" ; ntohl()--"Network to Host Long" 
 在这里, h表示"host" ,n表示"network",s 表示"short",l表示 "long"。
 打开socket 描述符、建立绑定并建立连接 
  
   socket函数原型为: 
  int socket(int domain, int type, int protocol); 
 
 domain参数指定socket的类型,一般为AF_INET,type可以是SOCK_STREAM
或SOCK_DGRAM,分别表示TCP连接和UDP连接;protocol通常赋值"0"。Socket()调用返回一个整型socket描述符,你可
以在后面的调用使用它。 
  一旦通过socket调用返回一个socket描述符,你应该将该socket与你本机上的一个端口相关联(往往当你在设计服务器端程序时需要调用该函数。随后你就可以在该端口监听服务请求;而客户端一般无须调用该函数)。 
   
   Bind函数原型为: 
  int bind(int sockfd,struct sockaddr *my_addr, int addrlen); 
  Sockfd是一个socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;addrlen常被设置为sizeof(struct sockaddr)。 
  最后,对于bind 函数要说明的一点是,你可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号: 
  my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */ 
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */ 
 
 通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将
my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。Bind()函数在成功被调用时返回0;遇到错
误时返回"-1"并将errno置为相应的错误号。另外要注意的是,当调用函数时,一般不要将端口号置为小于1024的值,因为1~1024是保留端口号,你可以使用大于1024中任何一个没有被占用的端口号。
  Connect()函数用来与远端服务器建立一个TCP连接,其函数原型为: 
  int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 
 
 Sockfd是目的服务器的sockt描述符;serv_addr是包含目的机IP地址和端口号的指针。遇到错误时返回-1,并且errno中包含相应
的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,内核
会自动选择一个未被占用的端口供客户端来使用。 
  Listen()??监听是否有服务请求 
  在服务器端程序中,当socket与某一端口捆绑以后,就需要监听该端口,以便对到达的服务请求加以处理。 
  int listen(int sockfd, int backlog); 
 
 Sockfd是Socket系统调用返回的socket
描述符;backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们(参考下文)。Backlog对队列中等待
服务的请求的数目进行了限制,大多数系统缺省值为20。当listen遇到错误时返回-1,errno被置为相应的错误码。
  故服务器端程序通常按下列顺序进行函数调用: 
  socket(); bind(); listen(); /* accept() goes here */
 
 accept()??连接端口的服务请求。 
  当某个客户端试图与服务器监听的端口连接时,该连接请求将排队等待服务器
accept()它。通过调用accept()函数为其建立一个连接,accept()函数将返回一个新的socket描述符,来供这个新连接来使用。而
服务器可以继续在以前的那个 socket上监听,同时可以在新的socket描述符上进行数据send()(发送)和recv()(接收)操作: 
  int accept(int sockfd, void *addr, int *addrlen); 
 
 sockfd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息
(某台主机从某个端口发出该请求);addrten通常为一个指向值为sizeof(struct
sockaddr_in)的整型指针变量。错误发生时返回一个-1并且设置相应的errno值。

  Send()和recv()??数据传输 
  这两个函数是用于面向连接的socket上进行数据传输。
 Send()函数原型为: 
  int send(int sockfd, const void *msg, int len, int flags); 
  Sockfd是你想用来传输数据的socket描述符,msg是一个指向要发送数据的指针。 
  Len是以字节为单位的数据的长度。flags一般情况下置为0(关于该参数的用法可参照man手册)。 
  char *msg = "Beej was here!"; int len, bytes_sent; ... ... 
  len = strlen(msg); bytes_sent = send(sockfd, msg,len,0); ... ... 
  Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。所以需要对send()的返回值进行测量。当send()返回值与len不匹配时,应该对这种情况进行处理。

 recv()函数原型为: 
  int recv(int sockfd,void *buf,int len,unsigned int flags); 
  Sockfd是接受数据的socket描述符;buf 是存放接收数据的缓冲区;len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数,或当出现错误时,返回-1并置相应的errno值。
 Sendto()和recvfrom()??利用数据报方式进行数据传输 
  在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址,sendto()函数原型为: 
  int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen); 
  该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。 
 Recvfrom()函数原型为: 
  int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); 
 
 from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof(struct
sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或
当出现错误时返回-1,并置相应的errno。 
  应注意的一点是,当你对于数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。
  Close()和shutdown()??结束数据传输 
  当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:close(sockfd); 
  你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
  int shutdown(int sockfd,int how); 
  Sockfd的含义是显而易见的,而参数 how可以设为下列值: 
  ?0-------不允许继续接收数据 
  ?1-------不允许继续发送数据 
  ?2-------不允许继续发送和接收数据,均为允许则调用close () 
  shutdown在操作成功时返回0,在出现错误时返回-1(并置相应errno)。
 DNS??域名服务相关函数 
  由于IP地址难以记忆和读写,所以为了读写记忆方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数gethostbyname()就是完成这种转换的,函数原型为: 
  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中的第一个地址*/


以上是一些基本讲解,下面附上一段源代码(编译OK的):作为一个服务器监听客户端的请求,服务器运行时后跟一个代表端口号的参数(编译后类似这样执行:server 8088)

------------------------------------------------------------------------------------------------------------------------------------------------msg.h

#ifndef __MSG_H__
#define __MSG_H__
#include <sys/types.h>
typedef struct{
char  head[9];
char  buff[512];
char  checksum;
}Msg;

extern int write_msg(int sockfd, char *buff, size_t len);
extern int read_msg(int sockfd, char *buff, size_t  len);

#endif
------------------------------------------------------------------------------------------------------------------------------------------------msg.h


------------------------------------------------------------------------------------------------------------------------------------------------server.c

#include "msg.h"
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>


int sockfd;


void do_service(int fd)
{
char  buff[512];
while(1){
memset(buff, 0, sizeof(buff));
if(read_msg(fd, buff, sizeof(buff)) < 0){
fprintf(stderr, "error protocol or error checksum\n");
}else{
printf("%s\n", buff);
if(write_msg(fd, buff, sizeof(buff)) < 0){
fprintf(stderr, "error protocol or error checksum\n");
}
}
}
}


void sig_handler(int signo)
{
if(SIGINT == signo){
printf("server will be closed!\n");
close(sockfd);
exit(1);
}
}


int main(int argc, char **argv)
{
if(argc < 2){
fprintf(stderr, "usage:%s #port\n", argv[0]);
exit(1);
}
if(signal(SIGINT, sig_handler) == SIG_ERR){
fprintf(stderr, "signal:%s\n", strerror(errno));
exit(1);
}
//1. construct the socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
fprintf(stderr, "socket:%s\n", strerror(errno));
exit(1);
}
//2. construct the socket address struct
struct sockaddr_in serveraddr;
//3. fill the ip and port into socket address struct
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
serveraddr.sin_addr.s_addr = INADDR_ANY;
//4. bind the socket address and socket
if(bind(sockfd, (struct sockaddr*)&serveraddr, 
sizeof(serveraddr)) < 0){
fprintf(stderr, "%s\n", strerror(errno));
exit(1);
}
//5. notify the OS to accept the connection request form client
if(listen(sockfd, 10) < 0){
fprintf(stderr, "%s\n", strerror(errno));
exit(1);
}
while(1){
//6. accept the connection request from queue and get the new socket fd
int fd = accept(sockfd, NULL, NULL);
if(fd < 0){
fprintf(stderr, "accept:%s\n", strerror(errno));
continue;
}
pid_t  pid = fork();
if(pid < 0){continue;}
else if(pid == 0){
close(sockfd);
do_service(fd);
break;
}else{
close(fd);
}
}
}

------------------------------------------------------------------------------------------------------------------------------------------------server.c

------------------------------------------------------------------------------------------------------------------------------------------------msg.c(原来落下了,补充)
#include "msg.h"
#include <memory.h>
#include <unistd.h>
#include <string.h>


static unsigned char msg_check(Msg *m)
{
unsigned char s = 0;
int i = 0;
for(; i < sizeof(m->head); i++){
s += m->head[i];
}
for(i = 0; i < sizeof(m->buff); i++){
s += m->buff[i];
}
return s;
}


int write_msg(int sockfd, char *buff, size_t len)
{
Msg m;
memset(&m, 0, sizeof(m));
strcpy(m.head, "wangwang");
memcpy(m.buff, buff, len);
m.checksum = msg_check(&m);
if(write(sockfd, &m, sizeof(m)) != sizeof(m)){
return -1;
}
return sizeof(m);
}


int read_msg(int sockfd, char *buff, size_t  len)
{
Msg m;
memset(&m, 0, sizeof(m));
ssize_t size;
if((size=read(sockfd, &m, sizeof(m))) < 0){
return -1;
}else if(size == 0){
return 0;
}
unsigned char s = msg_check(&m);
if((s == (unsigned char)m.checksum)
&&(!strcmp("wangwang", m.head))){
memcpy(buff, m.buff, len);
return sizeof(m);
}
return -1;
}

------------------------------------------------------------------------------------------------------------------------------------------------msg.c




















                
                

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值