从“hello”开始去分析linux下的socket编程

来源:互联网 发布:电信网络诈骗类型 编辑:程序博客网 时间:2024/06/05 10:29

如题,从一个简单示例去分析socket的编程。

首先给出源码

server.c

#include <stdlib.h>#include <sys/types.h>#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>int main(){int sfp,nfp;struct sockaddr_in s_add,c_add;int sin_size;unsigned short portnum=0x8888;printf("Hello,welcome to my server !\r\n");sfp = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sfp){    printf("socket fail ! \r\n");    return -1;}printf("socket ok !\r\n");bzero(&s_add,sizeof(struct sockaddr_in));s_add.sin_family=AF_INET;s_add.sin_addr.s_addr=htonl(INADDR_ANY);s_add.sin_port=htons(portnum);if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr))){    printf("bind fail !\r\n");    return -1;}printf("bind ok !\r\n");if(-1 == listen(sfp,5)){    printf("listen fail !\r\n");    return -1;}printf("listen ok\r\n");while(1){sin_size = sizeof(struct sockaddr_in);nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);if(-1 == nfp){    printf("accept fail !\r\n");    return -1;}printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",ntohl(c_add.sin_addr.s_addr),ntohs(c_add.sin_port));if(-1 == write(nfp,"hello,welcome to my server \r\n",32)){    printf("write fail!\r\n");    return -1;}printf("write ok!\r\n");close(nfp);}close(sfp);return 0;}
client.c

#include <stdlib.h>#include <sys/types.h>#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <string.h>int main(){int cfd;int recbytes;int sin_size;char buffer[1024]={0};struct sockaddr_in s_add,c_add;unsigned short portnum=0x8888;printf("Hello,welcome to client !\r\n");cfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == cfd){    printf("socket fail ! \r\n");    return -1;}printf("socket ok !\r\n");bzero(&s_add,sizeof(struct sockaddr_in));s_add.sin_family=AF_INET;s_add.sin_addr.s_addr= inet_addr("192.168.3.124");s_add.sin_port=htons(portnum);printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port);if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr))){    printf("connect fail !\r\n");    return -1;}printf("connect ok !\r\n");if(-1 == (recbytes = read(cfd,buffer,1024))){    printf("read data fail !\r\n");    return -1;}printf("read ok\r\nREC:\r\n");buffer[recbytes]='\0';printf("%s\r\n",buffer);getchar();close(cfd);return 0;}

gcc server.c -o server

gcc client.c -o  client



下面对服务器端和客户端进行分析

server.c中

------------------------------------------------------------------------

第一个函数

sfp = socket(AF_INET, SOCK_STREAM, 0);
domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIXAF_INET ).
AF_UNIX 只能够用于单一的 Unix 系统进程间通信,AF_INET 是针对 Internet ,因而可以允
许在远程 主机之间通信(当我们man socket 时发现 domain 可选项是 PF_*而不是AF_*,
glibc posix 的实现 所以用 PF 代替了 AF,不过我们都可以使用的).
type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM)SOCK_STREAM
表明我们用的是 TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.SOCK_DGRAM
表明我们用的是 UDP 协议,这样只会提供定长的,不可靠,无连接的通信.
protocol:由于我们指定了type,所以这个地方我们一般只要用0 来代替就可以了
socket 为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,errno 可知道出错
的详细情况.

sfd作为文件操作符返回,便于后面函数的调用。
第二个函数
bzero(&s_add,sizeof(struct sockaddr_in));
函数原型

extern void bzero(void *s, int n);将s为地址的n个字符置0;

一个小栗子

      #include <string.h>      main()      {        struct        {          int a;          char s[5];          float f;        } tt;                char s[20];                bzero(&tt,sizeof(tt));  // struct initialization to zero        bzero(s,20);                 return 0;      }

-----------------------------------------------------------------------

第三个函数
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))

int bind(int sock_fd,struct sockaddr_in *my_addr, int addrlen);
功能说明:
   将套接字和指定的端口相连。成功返回0,否则,返回-1,并置errno.
参数说明:
    sock_fd是调用socket函数返回值,
  my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;
  struct sockaddr_in结构类型是用来保存socket信息的:
  struct sockaddr_in {
  short int sin_family;
  unsigned short int sin_port;
  struct in_addr sin_addr;
  unsigned char sin_zero[8];
  };
    addrlen为sockaddr的长度。
看看s_addr是什麽,

struct sockaddr_in s_add,c_add;是sockaddr这个结构体,但是怎么会强制转换成struct sockaddr这个结构体

来分别看看这两个结构体

struct sockaddr    //16bytes{unsigned short sa_family;   //2byteschar           sa_data[14];  //14bytes}struct sockaddr_in   //16bytes{unsigned short sin_family;  //2bytesunsigned short sin_port;    //2bytesstruct in_addr sin_addr;    //4bytesunsigned char  sin_zero[8]; //8bytes}struct in_addr{unsigned int s_addr;}

可以看出sockaddr和sockaddr_in的字节数是一致的,在tcpip中我们所写ip端口呀都是合并起来用的,而为了输入方便,在_in结构体中分开,使用时再强制转换。

s_add.sin_family=AF_INET;s_add.sin_port=htons(portnum);s_add.sin_addr.s_addr=htonl(INADDR_ANY);
上面为初始化这个结构体,看看htons这个函数

我们知道网络字节顺序是大端的,而主机字节顺序是小端的,所以有一组函数用于两者的转化

unsigned long int htonl(unsigned long int hostlong)unsigned short int htons(unisgned short int hostshort)unsigned long int ntohl(unsigned long int netlong)unsigned short int ntohs(unsigned short int netshort)
在这四个转换函数中,h代表 host , n 代表 network . s 代表 short , l 代表 long 第一个函
数的意义是将本机器上的 long 数据转化为网络上的 long. 其他几个函数的意义也差不多.

portnum是我们自己设置的,INADDR_ANY是一个宏,INADDR_ANY
表示可以和任何的主机通信

----------------------------------------------------

第四个函数

if(-1 == listen(sfp,5))

sockfd:bind 后的文件描述符.
backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时,使用这个表示
可以介绍的排队长度. listen函数将 bind 的文件描述符变为监听套接字.返回的情况和bind
.

默认情况下,内核会认为socket函数的创建的描述符对应于主动套接字,调用listen函数告诉内核,描述符是被服务器而不是客户端使用的

------------------------------------------------

到这服务器的初始工作就完成了,等到客户端连接


先来看看client.c

--------------------------------------------------------

第一个函数

cfd = socket(AF_INET, SOCK_STREAM, 0);

一样去理解;

---------------------------------------------------------

第二个函数

bzero(&s_add,sizeof(struct sockaddr_in));

一样去理解

--------------------------------------------------------

s_add.sin_family=AF_INET;s_add.sin_addr.s_addr= inet_addr("192.168.3.124");s_add.sin_port=htons(portnum);
这里有出现了inet_addr

IP地址转换
有三个函数将数字点形式表示的字符串IP地址与32位网络字节顺序的二进制形式的IP地址进行转换
(1) unsigned long int inet_addr(const char * cp):该函数把一个用数字和点表示的IP地址的字符串转换成一个无符号长整型,如:struct sockaddr_in ina
ina.sin_addr.s_addr=inet_addr("202.206.17.101")
该函数成功时:返回转换结果;失败时返回常量INADDR_NONE,该常量=-1,二进制的无符号整数-1相当于255.255.255.255,这是一个广播地址,所以在程序中调用iner_addr()时,一定要人为地对调用失败进行处理。由于该函数不能处理广播地址,所以在程序中应该使用函数inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函数将字符串形式的IP地址转换成二进制形式的IP地址;成功时返回1,否则返回0,转换后的IP地址存储在参数inp中。
(3) char * inet_ntoa(struct in_addr in):将32位二进制形式的IP地址转换为数字点形式的IP地址,结果在函数返回值中返回,返回的是一个指向字符串的指针。

-----------------------------------------------------------------------

第三个函数

if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
sockfd:socket 返回的文件描述符.
serv_addr:储存了服务器端的连接信息.其中sin_add 是服务端的地址
addrlen:serv_addr 的长度
connect 函数是客户端用来同服务端连接的.成功时返回0,sockfd 是同服务端通讯的文件
描述符 失败时返回-1.

进行客户端程序设计无须调用 bind(),因为这种情况下只需知道目
的机器 的 IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体
为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到 打断口。
Connect 函数启动和远端主机的直接连接。 只有面向连接的客户程序使用 socket时才
需要将此 socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从
不启动一个连接,它只是被动的在协议端口监听客户的请求

连接完成后 就可以通行了

-------------------------------------------------------------------------------

回到server

第五个函数

nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);

accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用
accept 函数,然后睡眠并等待客户的连接请求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd 是被监听的 socket 描述符,addr 通常是一个指向 sockaddr_in 变量的指针,
该变量用来存放提出连接请求服务的主机的信息(某 台主机从某个端口发出该请求);
addrten 通常为一个指向值为 sizeof(struct sockaddr_in)的整型指针变量。

返回的是客户端的socket描述子,

----------------------------------------------------------------------------
第六个函数
if(-1 == write(nfp,"hello,welcome to my server \r\n",32))

write函数
syntax:
    ssize_t write(int fd,const void *buf,size_t nbytes)
功能说明:
    write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量.
    在网络程序中,当我们向套接字文件描述符写时有俩种可能:
      1)write的返回值大于0,表示写了部分或者是全部的数据.
      2)返回的值小于0,此时出现了错误.需要根据错误类型来处理.
        如果错误为EINTR表示在写的时候出现了中断错误.
        如果错误为EPIPE表示网络连接出现了问题.

------------------------------------------

server写后,client需要去读

client中

if(-1 == (recbytes = read(cfd,buffer,1024)))

ssize_t read(int fd,void *buf,size_t nbyte)
函数说明:
    read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.
    如果错误为EINTR说明读是由中断引起的,
    如果错误是ECONNREST表示网络连接出了问题.

通过connect fd已经和服务器取得联系

-----------------------------

函数

close(cfd);没什么问题

------------------------------------------------------------------------------------------------------------

上面是服务器发,客户端收,下面再看看两者相互通信的情况

server_2.c

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#define MAXLINE 4096int main(int argc, char** argv){    int    listenfd, connfd;    char    recvline[4096], sendline[4096];    struct sockaddr_in     servaddr;    char    buff[4096];    int     n;    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){    printf("create socket error: %s(errno: %d)/n",strerror(errno),errno);    exit(0);    }    memset(&servaddr, 0, sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    servaddr.sin_port = htons(6660);    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){    printf("bind socket error: %s(errno: %d)/n",strerror(errno),errno);    exit(0);    }    if( listen(listenfd, 10) == -1){    printf("listen socket error: %s(errno: %d)/n",strerror(errno),errno);    exit(0);    }    printf("======waiting for client's request======\n");    if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);         exit(0);    }while(1){    n = recv(connfd, buff, MAXLINE, 0);    buff[n] = '\0';    printf("recv msg from client: %s\n", buff);    printf("send msg to client: \n");    fgets(sendline, 4096, stdin);    if( send(connfd, sendline, strlen(sendline), 0) < 0)    {    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);    exit(0);    }}    close(listenfd);}

client_2.c

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#define MAXLINE 4096int main(int argc, char** argv){    int    sockfd;    char    recvline[4096], sendline[4096];    struct sockaddr_in    servaddr;    char    buff[4096];    int n;    if( argc != 2){    printf("usage: ./client <ipaddress>/n");    exit(0);    }    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){    printf("create socket error: %s(errno: %d)/n", strerror(errno),errno);    exit(0);    }    memset(&servaddr, 0, sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(6660);    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){    printf("inet_pton error for %s/n",argv[1]);    exit(0);    }    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){    printf("connect error: %s(errno: %d)/n",strerror(errno),errno);    exit(0);    }while(1){    printf("send msg to server: \n");    fgets(sendline, 4096, stdin);    if( send(sockfd, sendline, strlen(sendline), 0) < 0)    {    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);    exit(0);    }    n = recv(sockfd, buff, MAXLINE, 0);    buff[n] = '\0';    printf("recv msg from server: %s\n", buff);}    close(sockfd);    exit(0);}

下面的使用recv和send两个函数发送的

0 0
原创粉丝点击