[gcc编程] socket编程——TCP/UDP数据传输

来源:互联网 发布:手机蟑螂魔术软件 编辑:程序博客网 时间:2024/06/08 18:32
socket()——生成socket句柄
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol); 
domain : "AF_INET"
type : SOCK_STREAM(TCP),SOCK_DGRAM (UDP)
protocol:0
返回值:socket句柄(整型);

(注意:有很多种 domain、type,请看 socket() 的 man帮助。
 另一个方式去得到 protocol。同 时请查阅 getprotobyname() 的 man帮助。)int clifd;

if ((clifd = socket(AF_INET,SOCK_DGRAM(或者SOCK_STREAM),0))< 0)
    {
        printf("create socket error!\n");
        exit(1);
    }



close(clifd);


 
   bind()——将本端sockaddr_in(赋值后)强制转换成sockaddr类型,绑定到socket 句柄上
int bind(int sockfd, struct sockaddr *my_addr, intaddrlen);   sockfd = socket(AF_INET,SOCK_STREAM, 0);

   my_addr.sin_family =AF_INET;                
  my_addr.sin_port =htons(MYPORT);            
  my_addr.sin_addr.s_addr =inet_addr("132.241.5.10");  
 bzero(&(my_addr.sin_zero));     

  bind(sockfd, (struct sockaddr*)&my_addr,sizeof(struct sockaddr))bind ()的作用:
  作为client端发包时,bind上的是源地址和源端口
  作为server端收包时,是lisen的地址和listen的端口


    connect()——将对端sockaddr_in(赋值后)强制转换成sockaddr类型,绑定到socket 句柄上
int connect(int sockfd, struct sockaddr *serv_addr, intaddrlen);connect(sockfd, (structsockaddr *) &dest_addr,sizeof(struct sockaddr));connect()的作用
   TCP下发起连接——在STREAM下(TCP),connect()会企图建立连接attempt to establish aconnection
   UDP下设对端地址和端口——在DAGRAM下(UDP),connect()的作用只是设peeraddress/port,而不建立连接


    实验证明:TCP情况下(SOCK_STREAM),connect()bind(),二者都不能少
    实验证明:UDP情况下(SOCK_DGRAM),bind()可以不要,connect()也可以被sendto()代
例1:TCP下将bind()函数注释掉,无法建立连接
      if ((clifd = socket(AF_INET,SOCK_STREAM,0)) <0)
      {
             printf("create socket error!\n");
             exit(1);
      }
      bzero(&cliaddr,sizeof(cliaddr));
      cliaddr.sin_family = AF_INET;
      cliaddr.sin_port = htons(0);
      cliaddr.sin_addr.s_addr = htons(INADDR_ANY);

      bzero(&servaddr,sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      inet_aton(argv[1],&servaddr.sin_addr);
      servaddr.sin_port = htons(SERVER_PORT);





      if (connect(clifd,(struct sockaddr*)&servaddr,socklen) < 0)
      {
             printf("can't connect to %s!\n",argv[1]);
             exit(1);
      [root@nm socket]# ./client 10.4.3.55
can't connect to 10.4.3.55!例2:将上例改成SOCK_DGRAM(UDP),就好了。
 if ((clifd = socket(AF_INET,SOCK_DGRAM,0)) <0)
      {
             printf("create socket error!\n");
             exit(1);
      }[root@nm socket]# ./client 10.4.1.105
sent 14 bytes to  10.4.1.105UDP不需要BIND (其实连CONNECT也可省略,就剩SENDTO即可),TCP则BIND,CONNECT缺一不可


    UDP(SOCK_DGRAM)下,sendto()函数和connect()函数冲突,用sendto,就不能用connect()
发UDP包100个,可是每发1个,就停止了,出错。
     if ((clifd = socket(AF_INET,SOCK_DGRAM,0)) <0)
      {
             printf("create socket error!\n");
             exit(1);
      }

      bzero(&cliaddr,sizeof(cliaddr));
      cliaddr.sin_family = AF_INET;
      cliaddr.sin_port = htons(0);
      cliaddr.sin_addr.s_addr = htons(INADDR_ANY);

      bzero(&servaddr,sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      inet_aton(argv[1],&servaddr.sin_addr);
      servaddr.sin_port = htons(SERVER_PORT);

      if (connect(clifd,(struct sockaddr*)&servaddr,socklen) < 0)
      {
             printf("can't connect to %s!\n",argv[1]);
             exit(1);
           

strcpy(message,"this is a test");
for(i=0;i<100;i++){
if ((numbytes=sendto(clifd, message, strlen(message), 0, (structsockaddr *)&servaddr, sizeof(struct sockaddr))) ==-1) {
             printf("error send message %s!",message);
             exit(1);
            }
printf("sent %d bytes %s to %s\n",numbytes,message,inet_ntoa(servaddr.sin_addr));
 }[root@nm socket]# gcc -o client client.c
[root@nm socket]# ./client 10.4.1.105
sent 14 bytes this is a test to  10.4.1.105

为什么不继续传了?  解决:删掉connect(),直接用sendto();
  
结果:
[root@nm socket]# ./client 10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to  10.4.1.105
sent 14 bytes this is a test to 10.4.1.105UDPsendto发包,尽管配了connect()系统也不报错,但connect函数会使UDP包无法连续发


   UDP到底怎么发包?
  • 或者直接sendto();
  • 或者conncet() + send() 代替 sendto()


    总结TCP,UDP发包的调用函数过程
TCP发包 UDP发包socket()
bind()
connect()
send()
close(clifd)socket()
sendto()
close(clifd) socket()
connect()
send()
close(clifd)

   sizeof(struct sockaddr) 的含义:connect()将不会将多余的字节给 socket


    关于发包的随机绑定源端口,TCP和UDP做法不同
   TCP下,bind(),可以通过 cliaddr.sin_port = htons(0);实现TCP自动绑定源端口
   UDP下,sendto自动绑定源端口,或connect(),自动绑定,再send()


    int shutdown(int sockfd, inthow);  关闭通讯
how 的值是下面的其中之 一:
  0 – 不允许接受
  1 – 不允许发送
  2 – 不允许发送和接受(和 close() 一样)





    server端 的处理过程
  • 1。bind() 捆绑本地listen 端口号
  • 2。listen()  开始听
  • 3。accept()  处理


    listen()
int listen(int sockfd, intbacklog); int backlog: 队列长度,队列中允许的连接数目
            进入的连接在队列中会一直等待直到被接受 accept()

    accept()
int accept(int sockfd, sockaddr_in *addr, int *sin_size);   注意,是sockaddr_in,所以不需要强制转化成sockaddr类型
    int*sin_size:是 sizeof(structsockaddr_in)的指针化
   返回值:新句柄
   以后send()和recv()中应该使用accept()产生的新的socket 句柄new_fd,
  原有句柄sockfd继续用于listen
 
   accept 全过程
  •    远端client通过一个server端的listen端口连接进来
  •    它的connection将加入到等待接受的队列中
  •     accept()告诉它你有空闲的连接。
  •    accept()将返回一个新的socket句柄!
  •    这样你就有两个socket了:
   原来的一个还在listen
   新的一个在准备发送 (send()) 和接收 ( recv()) 数据 


    bind 的地址和端口
  •    发包时,是源地址和源端口
  •    收包时,是lisen的地址和listen的端口(也是源地址和端口)
总之,bind的都是本端的地址和端口


    一个完整的server 端bind+listen+accept
#include <string.h>;
#include <sys/socket.h>;
#include <sys/types.h>;

#define MYPORT3490 
#define BACKLOG10         

main() 

    int sockfd,new_fd    

  struct sockaddr_in my_addr,their_addr;
    intsin_size; 

    sockfd =socket(AF_INET, SOCK_STREAM, 0);

   my_addr.sin_family = AF_INET;
   my_addr.sin_port =htons(MYPORT);   
   my_addr.sin_addr.s_addr = INADDR_ANY;   
       任意地址,即listen本机所有接口
   bzero(&(my_addr.sin_zero));                 

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

   listen(sockfd, BACKLOG);开始listen

   in_size = sizeof(struct sockaddr_in);
   需要单独一个sizeof变量,因为accept需要传的是指针

    new_fd = accept(sockfd,&their_addr,&sin_size);  
    注意这里accept的参数their_addr没经过任何赋值,是空的

    以后send()和 recv()中你应该使用accept()产生的新的socket 句柄new_fd              

   只想让一个连接进来——accpet()后,使用 close()去关闭原来的socket句柄
  int sockfd,new_fd;

   sockfd = socket(AF_INET,SOCK_STREAM, 0);
   new_fd = accept(sockfd,&their_addr,&sin_size);           
   close(sockdf);  


   send()
通常用于tcp的stream,必须与bind和connect配合
int send(int sockfd, const void *msg, int len, intflags); const void *msg:要发的信息的指针
msg可以是任何类型的指针,比如一个字符串指针,或者一个struct指针(一般packet都是一个struct)

int len :要发的信息的长度

flags: 设置为0。

返回值是实际发送的数据的字节数--它可能小于你要求发送的数目char *msg = "helloworld"; 
int len, bytes_sent;

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

    recv()
int recv(int sockfd, void *buf, int len, unsigned intflags); void *buf:    存接收的信息的缓冲区的地址(指针)
int len: 缓冲区的最大max长度
         (如果buffer是一个数组char s[30],则len就是30)
flags: 设置为0char pack[100];
recv(sockfd,&pack,sizeof(pack),0);

   sendto()
用于udp的DGRAM不用bind()和connect()函数的情况下
sendto,recvfrom与send,recv不同之处,要加上structsockaddr参数,即替代了bind()的作用
int sendto(int sockfd, const void *msg, int len, unsigned intflags, const struct sockaddr *to,int tolen); const void *msg:要发的信息的指针
msg可以是任何类型的指针,比如一个字符串指针,或者一个struct指针(一般packet都是一个struct)

int len:要发的信息的长度

flags: 设置为00

const struct sockaddr *to:  要强制地址转换
int tolen:  sizeof(struct sockaddr)

返回值:实际发出的字节数例1:发一串字符的UDP包
if ((numbytes=sendto(clifd, message, strlen(message), 0,(struct sockaddr *)&servaddr,sizeof(struct sockaddr))) == -1){
             printf("error send message %s!",message);
             exit(1);
            }例2:发一个正经的包(一个cisco netflow包)
if ((numbytes=sendto(clifd, &pkts,24+48*FLOWCOUNT,0, (struct sockaddr *)&servaddr, socklen)) == -1){
             printf("error send !\n");
             printf("Wait Error:%s\n",strerror(errno));
             exit(1);
               }
recvfrom()
int recvfrom(intsockfd, void *buf, int len, unsigned int flags, struct sockaddr*from, int*fromlen);void *buf:    存接收的信息的缓冲区的地址(指针)
int len: 缓冲区的最大max长度
         (如果buffer是一个数组char s[30],则len就是30)
flags: 设置为0

const struct sockaddr *from: 它的内容是接收到的包IP地址和端口信息
要强制地址转换
int fromlen:  sizeof(struct sockaddr) 的指针


    UDP不需要listen(),必须直接recvfrom(),有listen会报错
[root@nm server]# ./server
call listen failure!
-1改成TCP
if ((servfd = socket(AF_INET,SOCK_STREAM,0)) <0)[root@nm server]# ./server

   收发会不会存在“对不齐”的问题?
1。对齐问题,sizeof会自动解决
2。只要收端和发端struct定义相同,就不会有问题。

   errno和strerror(errno)
系统函数返回值是-1,这时一个系统全局变量errno就会被赋值
立刻执行strerror(errno),会返回一个错误描述字符串
if ((numbytes=sendto(clifd, (char*)&pkts,24+48*FLOWCOUNT, 0, (struct sockaddr*)&servaddr, socklen)) == -1) {
             printf("Wait Error:%s\n",strerror(errno))
   }
    errno
errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之它。
主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误
#include <errno.h>
#include <math.h>
#include <stdio.h>
int main(void)
{
 errno =0;                  使用errno之前,我们最好将其设置为0(正常值)
 if (NULL == fopen("d:\\1.txt", "rb"))
 {
  printf("%d",errno);
 }
    perror              print a system error message
if (serverSocket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
)
  {
  perror("socket()");
  exit(1);
}
原创粉丝点击