Linux下socket单进程编程

1.socket的相关概念和思路
1.socket的功能和实现

功能:为不同机器上的两个进程提供通信机制

实现:在硬件层通过网络设备连接,在软件上通过标准的网络协议集TCP或UDP。

2.socket实现功能的思路

主要考虑面向连接的套接字编程方法,将其与打电话的过程进行类比。假设有一个人开通了一个电话号码,决定专门办理某种业务,那么他需要做的事情主要有:

买一个电话:相当于调用socket()函数,创建一个套接字

绑定电话号码:通过bind()函数绑定自己的IP地址

等待接听:使用listen()函数等待网络连接

接听电话:accept()函数接受连接

传输信息:recv()收到消息,send()发送消息

挂掉电话:close()函数关闭连接

如果有一个人打算电话办理业务,那么他需要:

买一个电话:还是调用socket()函数

打电话:connect()联系客服

传输信息:recv()send()分别收到和发送消息

挂掉电话:close()关闭连接

2.分析程序

由于还没学习线程的内容,目前的程序只能做一件事情,所以一端只接收,一端只发送。

先看部分结构和函数分析。

1.struct sockaddr_in sin_addr, pin_addr;

sockaddr_in是一个数据结构,具体为:

  struct   sockaddr_in   {  
                short int sin_family;     //2
                unsigned short int sin_port;     //2
                struct in_addr sin_addr;     //4
                unsigned char sin_zero[8];     //8
        };

sin_family代指协议族,在socket编程中只能使用AF_INET。AF_INET是IPv4网络协议的套接字类型。

sin_port存储端口号。一般计算机存储数据时有大端小端之别,高地址存放数据的尾端称为大端,低地址存放数据的尾端称为小端。为保证在网络上数据传输不出现错误,定义Internet上以大端模式进行传输。所以对于小端存放数据的机器,就需要进行转换。为保证操作的方便性,对于所有机器都进行转换。

sin_addr存放IP地址,使用in_addr数据结构。

sin_zero是为了让sockaddrsockaddr_in两个数据结构保持大小相同而保留的空字节。sockaddr是通用的网络地址结构,为一般函数原型采用。sockaddr_in是Internet网络的地址结构,编写程序时使用,在传递参数时转化为sockaddr类型。

sockaddr结构:

struct sockaddr {  
     sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   };
2.memset(&sin_addr, 0 , sizeof(sin_addr));

sin_addr的内容全部置零,注意memset函数位于头文件<string.h>中。

3.
sin_addr.sin_family = AF_INET;
sin_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sin_addr.sin_port = htons(PORT);

第一步,赋值协议族。

第二步,涉及到in_addr的结构:

struct in_addr {
    in_addr_t s_addr;
};

所以s_addr就表示IPv4地址

INADDR_ANY就是指定地址为0.0.0.0,这个地址表示不确定地址或者所有地址任意地址。如果服务器有多个IP地址,只绑定其中一个地址,就只能收到发送给一个地址的信息。而如果绑定0.0.0.0,就能收到所有IP地址收到的信息了。

htonl()是将32位的数据由主机字节序转化为网络字节序。

第三步,htons()的作用是将16位端口号由主机字节序转化为网络字节序

4.recv_sockfd = socket(AF_INET, SOCK_STREAM, 0)

socket函数原型:int socket(int domain, int type, int protocol)

所在的库:#include <sys/types.h>#include <sys/socket.h>

返回值:

若函数调用成功,则返回一个标识该套接字的文件描述符,失败则返回-1。

domain用于设置网络通信的域,socket()根据该参数选择通信协议的族。应该为AF_INET。

type用于设置套接字通信的类型。其中SOCK_STREAM为流式套接字。Tcp连接,提供序列化的、可靠的、双向连接的字节流。流式套接字在数据收发之前必须已经连接。

protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样该参数仅能设置为0。

5.bind(recv_sockfd, (struct sockaddr*) &sin_addr, sizeof(sin_addr))

bind函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

所在库:#include <sys/types.h>#include <sys/socket.h>

返回值:

成功,返回0;不成功,可能返回-1。

若函数调用成功,则socket将绑定特定地址。

recv_sockfd文件描述符。

addr,一个const struct sockaddr *类型的指针,指向要绑定给socket的协议地址。

6.listen(recv_sockfd, 5) < 0

函数原型:int listen(int sockfd, int backlog)

所在库:#include <sys/types.h>#include <sys/socket.h>

返回值:

成功,返回0;不成功,可能返回-1。

backlog为相应socket可以排队的最大连接个数。若连接数已经达到最大,当有客户端尝试connect()时,就会出现问题。

7.new_sockfd = accept(recv_sockfd, (struct sockaddr *) &pin_addr,&pin_addr_size)

accept函数原型为:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

struct sockaddr *addr,用于返回客户端的协议地址。

最后一个参数为客户端的地址长度。

8.recv(new_sockfd, buf, 200, 0)

函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);

返回值:如果返回值为0表示已经读到文件结束,小于零表示出现了错误。

服务端程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>

#define PORT 5559

int main(int argc, char **argv)
{
   //定义传输信息存储空间
   char buf[200];
   //定义服务端套接字描述符,客户端套接字描述符
   int recv_sockfd,new_sockfd;
   //定义带有服务端、客户端IP地址的数据结构
   struct sockaddr_in sin_addr, pin_addr;
   int len, pin_addr_size;
   pin_addr_size = sizeof(struct sockaddr);
   //给sin_addr进行赋值
   memset(&sin_addr, 0 , sizeof(sin_addr));
   sin_addr.sin_family = AF_INET;//协议族
   sin_addr.sin_addr.s_addr = htonl(INADDR_ANY);//0.0.0.0转化网络字节序
   sin_addr.sin_port = htons(PORT);//端口号转化网络字节序

   //得到服务端套接字描述符
   if ((recv_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
   {
      perror("socket");
      exit(1);
   }

   //服务端套接字描述符与IP地址绑定
   if (bind(recv_sockfd, (struct sockaddr*) &sin_addr, sizeof(sin_addr)) < 0)
   {
      perror("bind");
      exit(1);
   }

   //服务端套接字描述符开始接听信息
   if (listen(recv_sockfd, 5) < 0)
   {
      perror("listen");
      exit(1);
   }

    //始终准备连接,如果连接成功则始终等待接受消息
    while(1)
    {
        if ((new_sockfd = accept(recv_sockfd, (struct sockaddr *) &pin_addr,&pin_addr_size)) < 0)
        {
            perror("accept");
            exit(1);
        }
        while (1)
        {
            if (recv(new_sockfd, buf, 200, 0) == -1)
            {
                perror("recv");
                exit(1);
            }
            printf("received from client :%s\n", buf);
            memset(buf,'\0',sizeof(buf));
            sleep(1);
        }
        close(new_sockfd);  
    }
    return 0;
}

客户端程序:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#define PORT 5559

char str[20];

int main(int argc, char **argv)
{
   //定义传输信息存储空间
   char buf[200];

   char msg[256];
   //定义发送的套接字描述符
   int send_sockfd;
   //定义带有服务端IP地址的结构
   struct sockaddr_in pin_addr;

   //对于pin_addr开始赋值
   bzero(&pin_addr, sizeof(pin_addr));
   pin_addr.sin_family = AF_INET;
   //inet_addr将点分10进制的IP地址转换为长整数类型
   pin_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
   pin_addr.sin_port = htons(PORT);  

   //创建套接字描述符 
   if ((send_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
   {
      perror("socket");
      exit(1);
   }

   //连接服务端
   if (connect(send_sockfd, (void*) &pin_addr, sizeof(pin_addr)) == -1)
   {
      perror("connect");
      exit(1);
   }

   //始终等待发送数据
   while(1)
   {
      printf("please type msg:\n");
      scanf("%s",&str); 
      if (send(send_sockfd, str, strlen(str), 0) == -1)
      {
          perror("send");
          exit(1);
      }
   }
   close(send_sockfd);
   return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值