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

30 篇文章 0 订阅
21 篇文章 0 订阅

如题,从一个简单示例去分析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_UNIX AF_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;   //2bytes
	char           sa_data[14];  //14bytes
}

struct sockaddr_in   //16bytes
{
	unsigned short sin_family;  //2bytes
	unsigned short sin_port;    //2bytes
	struct in_addr sin_addr;    //4bytes
	unsigned 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 4096

int 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 4096

int 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值