2018-05-22 11:23:52 oyoung_2012 阅读数 5427
  • C语言/C++学习指南》Linux开发篇

    本篇面向Linux初级、但已掌握C/C++基本语法的学员,以CentOS为演示平台,介绍Linux下的C/C++开发技术。在内容编排上,先介绍Linux的安装、基本使用和配置,然后再开始介绍在Linux下的C/C++开发技术: gcc命令行、Makefile、gdb调试方法、动态库与静态库、pthread线程、进程、进程间通信、系统调用及杂项技术。(前四章免费)

    60811 人正在学习 去看看 邵发

说明

随着嵌入式开发在物联网行业中站的比重日益增加,Linux 环境下的C++也不断变得更为大众化。习惯了Window平台开发的开发人员, 都被Visual Studio的强大宠坏了, 无论是什么样的开发需求, 总能有现成的轮子可以直接拿来用。就好比这里要介绍的串口通信, 在Windows开发中, 无论是C++, MFC,还是C#, 巨硬大大都给我们做好了封装。可是在Linux下就没那么简单了,虽然开源, 但是很多的开发都偏底层,连一个标准库级别的串口通信SDK都没有,很是无奈。作为开发人员, 要么自己花时间熟悉底层接口然后造轮子,要么去开源社区找一些别人造好的轮子。而实际上,C++的开发大多对项目的耦合性比较大,很多别人造的轮子也都是在作者自己开发项目的过程中慢慢积累的,那些公开的接口如果别人拿来直接用,并不见得会很友好。所以大部分情况下都是开发者自己根据系统级别的api来自己造轮子

当然大多数情况下使用现有的串口库会节省很多时间, 本篇文章仅仅时为了记录学习Linux 环境下串口编程的一些基础原理
如果要使用现有库的话, ros-kinetic-serial 库应该是一个不错的选择

串口通信流程

1. 使用串口名(Linux下面通常为/dev/tty*, windows下通常为COM*)打开串口
2.  给打开的串口设定参数, 如 波特率、数据位、停止位、校验位等等
3.  向串口发送数据
4.  从串口中接收数据

需要使用到的一些接口

// 头文件
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
// 串口打开关闭与读写
int open(const char *name, int flag)
int write(int fd, const void *data, size_t size)
int read(int fd, void *data, size_t size)
int close(int fd)
// 串口配置相关
struct termios;
tcgetattr(int fd, struct termios* tios)
cfsetispeed(struct termios*tios,  int baudrate)
cfsetospeed(struct termios*tios, int baudrate)
int tcsetattr (int __fd, int __optional_actions, const struct termios *__termios_p)

示例

接口头文件

/**
* file: serialport.h
* created by oyoung on 2018/05
*/
#ifndef SERIALPORT_H
#define SERIALPORT_H

#include <string>
#include <vector>

struct termios;
class SerialPort
{
public:
    enum BaudRate {
        BR0 = 0000000,
        BR50 = 0000001,
        BR75 = 0000002,
        BR110 = 0000003,
        BR134 = 0000004,
        BR150 = 0000005,
        BR200 = 0000006,
        BR300 = 0000007,
        BR600 = 0000010,
        BR1200 = 0000011,
        BR1800 = 0000012,
        BR2400 = 0000013,
        BR4800 = 0000014,
        BR9600 = 0000015,
        BR19200 = 0000016,
        BR38400 = 0000017,
        BR57600 = 0010001,
        BR115200 = 0010002,
        BR230400 = 0010003,
        BR460800 = 0010004,
        BR500000 = 0010005,
        BR576000 = 0010006,
        BR921600 = 0010007,
        BR1000000 = 0010010,
        BR1152000 = 0010011,
        BR1500000 = 0010012,
        BR2000000 = 0010013,
        BR2500000 = 0010014,
        BR3000000 = 0010015,
        BR3500000 = 0010016,
        BR4000000 = 0010017
    };

    enum DataBits {
        DataBits5,
        DataBits6,
        DataBits7,
        DataBits8,
    };

    enum StopBits {
        StopBits1,
        StopBits2
    };

    enum Parity {
        ParityNone,
        ParityEven,
        PariteMark,
        ParityOdd,
        ParitySpace
    };

    struct OpenOptions {
        bool autoOpen;
        BaudRate baudRate;
        DataBits dataBits;
        StopBits stopBits;
        Parity parity;
        bool xon;
        bool xoff;
        bool xany;
        int vmin;
        int vtime;
    };

    static BaudRate BaudRateMake(unsigned long baudrate);

    static const OpenOptions defaultOptions;

    explicit SerialPort(const std::string& path, const OpenOptions options = defaultOptions);

    bool open();
    bool open(const std::string& path, const OpenOptions& options);

    bool isOpen() const;

    int write(const void *data, int length);
    int read(void *data, int length);


    void close();

    static std::vector<std::string > list();

protected:

    void termiosOptions(termios& tios, const OpenOptions& options);


private:
    std::string _path;
    OpenOptions _open_options;
    int _tty_fd;
    bool _is_open;
};


bool operator==(const SerialPort::OpenOptions& lhs, const SerialPort::OpenOptions& rhs);
bool operator!=(const SerialPort::OpenOptions& lhs, const SerialPort::OpenOptions& rhs);

#endif // SERIALPORT_H

实现文件

#include "serialport.h"
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

const SerialPort::OpenOptions SerialPort::defaultOptions = {
    true, //        bool autoOpen;
    SerialPort::BR9600, //    BaudRate baudRate;
    SerialPort::DataBits8, //    DataBits dataBits;
    SerialPort::StopBits1, //    StopBits stopBits;
    SerialPort::ParityNone,//    Parity parity;
    false,                  // input xon
    false,                  // input xoff
    false,                  // input xany
    0,                      // c_cc vmin
    50,                     // c_cc vtime
};

SerialPort::SerialPort(const std::string &path, const OpenOptions options)
    : _path(path), _open_options(options) {
    if(options.autoOpen) {
        _is_open = open(_path, _open_options);
    }
}


bool SerialPort::open() {
    return _is_open = open(_path, _open_options), _is_open;
}

bool SerialPort::open(const std::string &path, const OpenOptions &options) {

    if(_path != path) _path = path;
    if(_open_options != options) _open_options = options;

    _tty_fd = ::open(path.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
    if(_tty_fd < 0) {
        return false;
    }

    struct termios  tios;
    termiosOptions(tios, options);
    tcsetattr(_tty_fd, TCSANOW, &tios);
    tcflush(_tty_fd, TCIOFLUSH);
    return true;
}

void SerialPort::termiosOptions(termios &tios, const OpenOptions &options) {


    tcgetattr(_tty_fd, &tios);

    cfmakeraw(&tios);
    tios.c_cflag &= ~(CSIZE | CRTSCTS);
    tios.c_iflag &= ~(IXON | IXOFF | IXANY | IGNPAR);
    tios.c_lflag &= ~(ECHOK | ECHOCTL | ECHOKE);
    tios.c_oflag &= ~(OPOST | ONLCR);

    cfsetispeed(&tios, options.baudRate);
    cfsetospeed(&tios, options.baudRate);

    tios.c_iflag |= (options.xon ? IXON : 0)
            | (options.xoff ? IXOFF: 0)
            | (options.xany ? IXANY : 0);

    // data bits

    int databits[] =  {CS5, CS6, CS7, CS8};
    tios.c_cflag &= ~0x30;
    tios.c_cflag |= databits[options.dataBits];

    // stop bits
    if(options.stopBits == StopBits2) {
        tios.c_cflag |= CSTOPB;
    } else {
        tios.c_cflag &= ~CSTOPB;
    }

    // parity
    if(options.parity == ParityNone) {
        tios.c_cflag &= ~PARENB;
    } else {
        tios.c_cflag |= PARENB;

        if(options.parity == PariteMark) {
            tios.c_cflag |= PARMRK;
        } else {
            tios.c_cflag &= ~PARMRK;
        }

        if(options.parity == ParityOdd) {
            tios.c_cflag |= PARODD;
        } else {
            tios.c_cflag &= ~PARODD;
        }
    }

    tios.c_cc[VMIN] = options.vmin;
    tios.c_cc[VTIME] = options.vtime;
}

bool SerialPort::isOpen() const {
    return _is_open;
}

int SerialPort::write(const void *data, int length) {
    return ::write(_tty_fd, data, length);
}

int SerialPort::read(void *data, int length) {
    return ::read(_tty_fd, data, length);
}

void SerialPort::close() {
    ::close(_tty_fd);
    _is_open = false;
}

SerialPort::BaudRate SerialPort::BaudRateMake(unsigned long baudrate) {
    switch (baudrate) {
    case 50:
        return BR50;
    case 75:
        return BR75;
    case 134:
        return BR134;
    case 150:
        return BR150;
    case 200:
        return BR200;
    case 300:
        return BR300;
    case 600:
        return BR600;
    case 1200:
        return BR1200;
    case 1800:
        return BR1800;
    case 2400:
        return BR2400;
    case 4800:
        return BR4800;
    case 9600:
        return BR9600;
    case 19200:
        return BR19200;
    case 38400:
        return BR38400;
    case 57600:
        return BR57600;
    case 115200:
        return BR115200;
    case 230400:
        return BR230400;
    case 460800:
        return BR460800;
    case 500000:
        return BR500000;
    case 576000:
        return BR576000;
    case 921600:
        return BR921600;
    case 1000000:
        return BR1000000;
    case 1152000:
        return BR1152000;
    case 1500000:
        return BR1500000;
    case 2000000:
        return BR2000000;
    case 2500000:
        return BR2500000;
    case 3000000:
        return BR3000000;
    case 3500000:
        return BR3500000;
    case 4000000:
        return BR4000000;
    default:
        break;
    }
    return BR0;
}


std::vector<std::string> SerialPort::list() {
    DIR *dir;
    struct dirent *ent;
    dir = opendir("/dev");
    std::vector<std::string> ttyList;

    while(ent = readdir(dir), ent != nullptr) {
        if("tty" == std::string(ent->d_name).substr(0, 3)) {
            ttyList.emplace_back(ent->d_name);
        }
    }

    return ttyList;
}
bool operator==(const jzhw::SerialPort::OpenOptions& lhs, const jzhw::SerialPort::OpenOptions& rhs)
{
    return lhs.autoOpen == rhs.autoOpen
            && lhs.baudRate == rhs.baudRate
            && lhs.dataBits == rhs.dataBits
            && lhs.parity == rhs.parity
            && lhs.stopBits == rhs.stopBits
            && lhs.vmin == rhs.vmin
            && lhs.vtime == rhs.vtime
            && lhs.xon == rhs.xon
            && lhs.xoff == rhs.xoff
            && lhs.xany == rhs.xany;
}

bool operator!=(const SerialPort::OpenOptions& lhs, const SerialPort::OpenOptions& rhs){
    return !(lhs == rhs);
}

2011-01-13 14:55:00 hx_mraku 阅读数 1162
  • C语言/C++学习指南》Linux开发篇

    本篇面向Linux初级、但已掌握C/C++基本语法的学员,以CentOS为演示平台,介绍Linux下的C/C++开发技术。在内容编排上,先介绍Linux的安装、基本使用和配置,然后再开始介绍在Linux下的C/C++开发技术: gcc命令行、Makefile、gdb调试方法、动态库与静态库、pthread线程、进程、进程间通信、系统调用及杂项技术。(前四章免费)

    60811 人正在学习 去看看 邵发

java socket 与linux c通信问题
97w211 发表于: 2010-2-09 18:08 来源: 科技复兴

如题,用java做客户端,linux的c程序做服务器端。目前客户端向服务器端发送信息已经没问题,但是在添加服务器端向客户端发送信息的功能时却出现了死循环。在Eclipse调试了一下客户端发现是在in.readLine()卡住了,具体代码如下:
java 客户端:
Java code
package linux;

import java.net.*;
import java.*;
import java.util.*;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.*;
import java.io.BufferedReader;
import java.net.InetSocketAddress;
public class java_c_client{
        public static void main(String[] argv){
                Socket socket = null;
                InputStream in = null;
                BufferedReader input = null;
                byte [] b = new byte[5];
                String url = "219.216.101.210";
                int PORT = 9999;
                try{
                       
                        int num = -1;
                        socket = new Socket();
            socket.setReuseAddress(true);
            InetSocketAddress socketAddr = new InetSocketAddress(url, PORT);
                    System.out.println("ip==="+socket.getInetAddress()+"/t port=="+socket.getLocalPort());

            socket.connect(socketAddr);
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            System.out.println("ip==="+socket.getInetAddress()+"/t port=="+socket.getLocalPort());
            input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            in = socket.getInputStream();
          
            byte[] buffer = new byte[20];
            in.read(buffer, 0, 20);
                        String name = "hello";       
                        //out = socket.getOutputStream();
                       
                        while(num==-1){
                                //out = socket.getOutputStream();]
                                System.arraycopy(name.getBytes(),0,b,0,name.getBytes().length);//将name数据内容从0开始拷贝到字节数组b
                                dos.write(b);
                                if (input.readLine() != null) {
                                     System.out.println("服务器反馈信息:"+input.readLine());
                                }else {
                                    System.out.println("服务器无反馈!");
                                }
                             /*  for (int i = 0; i < buffer.length; i++) {
                                System.out.print(buffer[i]);
                            }*/
                                num ++;
                        }
                    if(num>-1){
                                dos.flush();
                                socket.close();
                        }
                       
                        System.out.println("finish closing ...........");
                }catch(Exception e){
                       
                        e.printStackTrace();
                }
        }
}

 

Linux C服务器端
C/C++ code
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>

#define MYPORT  9999
#define BACKLOG 10

int main()
{
  int sockfd, new_fd;
char msg[10] = "Got it!/n";
int length = strlen(msg);                                             /* listen on sock_fd, new connection on new_fd */
  struct sockaddr_in my_addr;                                      /* 地址信息 */
  struct sockaddr_in their_addr;                                   /* connector's address information */
  int sin_size;
  char buf[128];
  sockfd = socket(AF_INET, SOCK_STREAM, 0);                        /* 错误检查*/
  my_addr.sin_family = AF_INET;                                    /* host byte order */
  my_addr.sin_port = htons(MYPORT);                                /* short, network byte order */
  my_addr.sin_addr.s_addr = INADDR_ANY;                            /* auto-fill with my IP */
  bzero(&(my_addr.sin_zero),8);                                    /* zero the rest of the struct */
  bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
  listen(sockfd, BACKLOG);
/*
  sin_size = sizeof(struct sockaddr_in);
  //new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
  int num = -1;
  while(num==-1)
  {

     new_fd=accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
     int n=recv(new_fd,buf,2,0);
     num++;
  }
  printf("buf==%s",buf);
*/
sin_size = sizeof(struct sockaddr_in);

new_fd=accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
int n=recv(new_fd,buf,128,0);
if(n > 0)
{
  printf("buf==%s/n",buf);
printf("the length of message is:%d/n",length);
if(send(sockfd,msg,length+1,0) == -1)
perror("can't send message/n");
}

  close(new_fd);
  close(sockfd);
  return 0;
}

 

2020-01-05 22:49:44 wanggao_1990 阅读数 58
  • C语言/C++学习指南》Linux开发篇

    本篇面向Linux初级、但已掌握C/C++基本语法的学员,以CentOS为演示平台,介绍Linux下的C/C++开发技术。在内容编排上,先介绍Linux的安装、基本使用和配置,然后再开始介绍在Linux下的C/C++开发技术: gcc命令行、Makefile、gdb调试方法、动态库与静态库、pthread线程、进程、进程间通信、系统调用及杂项技术。(前四章免费)

    60811 人正在学习 去看看 邵发

主要分成

  1. 打开串口
  2. 关闭串口
  3. 发送字符、字符串
  4. 发送格式化字符串
  5. 等待接收字符的数量
  6. 接收一个字符
  7. 释放缓冲区

一、模块代码

1、打开串口

int serialOpen (const char *device, const int baud)
{
  struct termios options ;
  speed_t myBaud ;
  int     status, fd ;

  switch (baud)
  {
    case     50:	myBaud =     B50 ; break ;
    case     75:	myBaud =     B75 ; break ;
    case    110:	myBaud =    B110 ; break ;
    case    134:	myBaud =    B134 ; break ;
    case    150:	myBaud =    B150 ; break ;
    case    200:	myBaud =    B200 ; break ;
    case    300:	myBaud =    B300 ; break ;
    case    600:	myBaud =    B600 ; break ;
    case   1200:	myBaud =   B1200 ; break ;
    case   1800:	myBaud =   B1800 ; break ;
    case   2400:	myBaud =   B2400 ; break ;
    case   4800:	myBaud =   B4800 ; break ;
    case   9600:	myBaud =   B9600 ; break ;
    case  19200:	myBaud =  B19200 ; break ;
    case  38400:	myBaud =  B38400 ; break ;
    case  57600:	myBaud =  B57600 ; break ;
    case 115200:	myBaud = B115200 ; break ;
    case 230400:	myBaud = B230400 ; break ;
    default:
      return -2 ;
  }

  if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)
    return -1 ;

  fcntl (fd, F_SETFL, O_RDWR) ;

// Get and modify current options:
  tcgetattr (fd, &options) ;

    cfmakeraw   (&options) ;
    cfsetispeed (&options, myBaud) ;
    cfsetospeed (&options, myBaud) ;

    options.c_cflag |= (CLOCAL | CREAD) ;
    options.c_cflag &= ~PARENB ;
    options.c_cflag &= ~CSTOPB ;
    options.c_cflag &= ~CSIZE ;
    options.c_cflag |= CS8 ;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;
    options.c_oflag &= ~OPOST ;

    options.c_cc [VMIN]  =   0 ;
    options.c_cc [VTIME] = 100 ;	// Ten seconds (100 deciseconds)

  tcsetattr (fd, TCSANOW | TCSAFLUSH, &options) ;

  ioctl (fd, TIOCMGET, &status);

  status |= TIOCM_DTR ;
  status |= TIOCM_RTS ;

  ioctl (fd, TIOCMSET, &status);

  usleep (10000) ;	// 10mS

  return fd ;
}

2、关闭串口

void serialClose (const int fd)
{
  close (fd) ;
}

3、发送字符、字符串

void serialPutchar (const int fd, const unsigned char c)
{
  write (fd, &c, 1) ;
}

void serialPuts (const int fd, const char *s)
{
  write (fd, s, strlen(s)) ;
}

4、发送格式化字符串

void serialPrintf (const int fd, const char *message, ...)
{
  va_list argp ;
  char buffer [1024] ;
  
  va_start (argp, message) ;
    vsnprintf (buffer, 1023, message, argp) ;
  va_end (argp) ;

  serialPuts (fd, buffer) ;
}

5、等待接收字符的数量

int serialDataAvail (const int fd)
{
  int result ;

  if (ioctl (fd, FIONREAD, &result) == -1)
    return -1 ;

  return result ;
}

6、接收一个字符

int serialGetchar (const int fd)
{
  uint8_t x ;

  if (read (fd, &x, 1) != 1)
    return -1 ;

  return ((int)x) & 0xFF ;
}

6、释放缓冲区

void serialFlush (const int fd)
{
  tcflush (fd, TCIOFLUSH) ;
}

二、模块测试

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int running = 1;

void sig_handle(int sig)
{
   if(sig == SIGINT)  running = 0;
}

int main()
{    
    signal(SIGINT, sig_handle);    

    int fd;
    if(wiringPiSetup() < 0){
        printf("wiringPi setup failed.\n");
        return 1;
    }

    int baudrate = 115200;

	// 打开串口
    //if((fd = serialOpen("/dev/ttyS0", baudrate)) < 0){  
    if((fd = serialOpen("/dev/ttyAMA0",baudrate)) < 0){  
        printf("serial open failed.\n");
        return 1;
    }

    printf("serial test output ...\n");   
    serialPrintf(fd, "0123456789abcdef");  // 发送

    while(running)
    {
       int sz = serialDataAvail(fd);  // 待接收的字符数量
       
       if(sz > 0)
       {
         printf("size %d, ", sz);
         char *buff =(char*)malloc(sz);
         printf("recv: ");
         for(int i = 0; i < sz; i++){
            int c = serialGetchar(fd);
            //if(c != -1)
            buff[i] = c;  
         }
         printf("%s\n", buff);
         free(buff);

         serialPrintf(fd, buff);
       }
       else{
           usleep(50000); // 必要的延时
       }
    }

    serialClose(fd);
    printf("close serial.\n");

    return 0;
}
2019-05-24 23:32:52 black_kyatu 阅读数 294
  • C语言/C++学习指南》Linux开发篇

    本篇面向Linux初级、但已掌握C/C++基本语法的学员,以CentOS为演示平台,介绍Linux下的C/C++开发技术。在内容编排上,先介绍Linux的安装、基本使用和配置,然后再开始介绍在Linux下的C/C++开发技术: gcc命令行、Makefile、gdb调试方法、动态库与静态库、pthread线程、进程、进程间通信、系统调用及杂项技术。(前四章免费)

    60811 人正在学习 去看看 邵发

网络通信概述

计算机网络通信内容很多,这只是一个针对socket编程的基础知识概述总结

通信参考模型

互联网通信遵循一定的通信协议和通信机制,称为通信模型。
国际标准化的开放网络通信模型是OSI参考模型,但是OSI模型概念提出较晚,所以一直没有实现,只是停留在概念性的参考框架上。目前通用的模型为TCP/IP通信参考模型,以下主要介绍TCP/IP通信参考模型。

自顶向下的TCP/IP参考模型
应用层
  • 客户在应用层使用各种应用和协议进行通信,应用层实现了应用到应用之间的通信,即进程与进程之间的通信,通过端口识别不同的进程。
  • 常用的应用层协议包括http 超文本传输协议ftp 文件传输协议telnet 远程登录ssh 安全外壳协议stmp 简单邮件发送协议pop3 邮件收发协议。应用层程序员可以通过编程实现自己的通信协议。
  • 应用层的通信协议规定了用户传输的数据信息的格式,并附加了相关的格式控制信息等,称为报文
    • 应用层的报文传输由传输层实现。
传输层
  • 传输层负责实现端到端通信,即主机到主机之间的通信,源主机ip到目标主机ip
  • 传输层把应用层的报文按照大小划分为报文段并附加传输层协议的格式控制信息
  • 报文段由传输层交付网络层传输。
  • 传输层从协议主要是TCP协议和UDP协议
  • TCP协议通过三次握手(-- 在吗?-- 我在,你在吗?-- 我也在,我们开始吧),四次挥手(–我要走了 – 好的我知道了你走吧 – 我也要走了 – 好的再见)机制实现端到端的可靠连接,通过给报文加序号实现乱序重排,并通过滑动窗口协议实现出错重传
  • UDP协议属于无连接协议,不可靠,但是实时性高,只负责发送数据,不负责顺序发送和出错重传。
网络层
  • 网络层为传输层提供无连接的,尽最大努力交付的数据报服务,主要由IP协议,ICMP,IGMP协议和一系列路由方法协议组成。数据的可靠性需要传输层自己保证。
  • 网络层协议负责将传输层的报文段封装成ip分组(数据包),并实现路由寻径,找到一条从源主机到目的主机的通路。
  • 具体的数据流传输控制由链路层进行。
链路层
  • 链路层协议实现具体的二进制数据流传输,对上层掩盖了物理传输的具体细节,是TCP/IP模型的最底层
  • 链路层协议包括ARP,RARP等协议,实现了ip地址和物理mac地址的对应
  • 链路层根据物理网络状况将ip分组划分封装成数据帧进行传输,负责数据差错的检查和校验

socket编程预备知识

socket概述
  • socket是操作系统内核中的一种数据结构,用来在同一主机或不同主机的进程通信中标识不同进程。
  • Linux系统中的socket是一种特殊的I/O接口,同样由文件描述符进行管理,属于特殊类型的文件。 ,
  • 每一个 socket 都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。
  • 创建一个socket时只需要指明其通信域和通信协议类型即可
  • 通信域规定了socket的地址格式和通信范围
socket类型
  1. 流式 socket(SOCK_STREAM):用于 TCP 通信
  2. 数据报 socket(SOCK_DGRAM) :用于 UDP 通信
  3. 原始 socket(SOCK_RAW) :用于新的网络协议实现的测试等,可以直接访问网络层协议
socket地址类型

socke创建后需要绑定通信地址才能使用,socket的地址格式有两种如下:两种地址可以相互转化
1、sockaddr通用地址

struct sockaddr 
{
unsigned short sa_family; /*地址族*/
char sa_data[14]; /*14 字节的协议地址,包含该 socket 的 IP 地址和端口号。*/
};

sockaddr缺陷:sa_data把目标地址和端口信息混在一起,不利于网络通信
2、sockaddr_in网络地址

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 同样大小*/
};
//保存32位二进制ip地址的结构体
struct in_addr
{
unsigned long int s_addr; /* 32 位 IPv4 地址,网络字节序 */
};
数据存储字节序——大端小端
  • 计算机中的数据按字节存储,但是有很多数据常常占据多个字节,这种情况下就需要考虑字节的存储顺序,就像人们的书写顺序一样。
  • 如果先存高位,再存低位,即高位字节对应的内存地址编号小,从地址0读数据先读到的是高位时,称为大端模式。大端模式同样也符合人们的书写阅读习惯,从左到右先写高位再写低位,因此互联网通信中采用的都是大端模式
  • 如果先存低位,再存高位,即低位字节对应的内存地址编号小,就称为小端模式。主机中的字节序和处理器相关,市面上的主流处理器如intel都是小端模式。可以通过以下程序判断主机是大端还是小端模式。
    #include <stdio.h>  
    
    int main()        
    {   
    	int x=0x12345678; 
    	//占四个字节,如果是大端的话从低地址到高地址依次是0x12,0x34,0x56,0x78
    	char * p=(char *)&x;  
    	printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]);  
    	//从低位到高位按字节输出,如果是小端会得到:78,56,34,12
    }
    
  • 由于主机字节序和网络字节序常常有冲突,因此需要进行字节序的转换,才能正常进行网络通信,这里用到四个函数:
    • uint16_t htons(uint16_t host16bit):host to net short 的缩写,将16位的主机字节序转换为16位的网络字节序并返回,用于16位端口号从主机到网络的转换
    • uint16_t ntohs(uint16_t net16bit):net to host short 的缩写,将16位的网络字节序转换为16位的主机字节序并返回,用于16位端口号从网络到主机的转换
    • uint32_t htonl(uint32_t host32bit):host to net long 的缩写,将32位的主机字节序转换为32位的网络字节序并返回,用于32位ip地址的转换
    • uint32_t ntohl(uint32_t net32bit):net to host long 的缩写,将32位的网络字节序转换为32位的主机字节序并返回,用于32位ip地址的转换
ip地址格式转换

通常用户在表达ip地址时采用的是点分十进制或者是冒号分开的十进制 Ipv6 地址。而在socket 编程中使用的则是 32 位的网络字节序的二进制值,这就需要将这两个数值进行转换,转换用到如下函数:

只适用于ipv4的函数

  • int inet_aton(const char *straddr, struct in_addr *addrptr);
    将点分十进制数的 IP 地址(straddr)转换成为网络字节序的 32 位二进制数值(addrptr),成功,则返回 1,不成功返回 0
  • char *inet_ntoa(struct in_addr inaddr);
    将网络字节序的 32 位二进制数值inaddr转换为点分十进制的 IP 地址返回值
  • unsigned long int inet_addr(const char *straddr);
    将点分十进制数的 IP 地址(straddr)转换成为网络字节序的 32 位二进制数值返回值
    兼容ipv6的函数
  • int inet_pton(int family, const char *src, void *dst);
    ip地址src转为网络字节序的二进制数值,family 参数指定为 AF_INET,表示是 IPv4 协议,如果是 AF_INET6,表示 IPv6 协议
  • const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);
    将二进制数值src转为ip地址dst,len表示转换后的字符串长度
ip地址和域名(主机名)之间的转换

ip地址不方便记忆,因此实际用户访问网络时通过域名或主机名(局域网)访问,所以还需要域名(主机名)和ip地址的转换,准确的说是通过ip或者主机名获取到主机信息。

主机信息用结构体存放定义如下:

struct hostent
{
char *h_name; /*正式主机名*/
char **h_aliases; /*主机别名*/
int h_addrtype; /*主机 IP 地址类型 IPv4 为 AF_INET*/
int h_length; /*主机 IP 地址字节长度,对于 IPv4 是 4 字节,即 32 位*/
char **h_addr_list; /*二进制表示的主机的 IP 地址列表,一个域名或主机可能对应多个ip*/
}
  • struct hostent* gethostbyname(const char* hostname);
    参数为主机名,返回主机信息结构体指针,指针为NULL表示查找失败
  • struct hostent* gethostbyaddr(const char* addr,size_t len,int family);
    参数为ip地址的二进制表示,len为地址长度,family为地址类型

socket编程

TCP通信协议编程

TCP 协议 socket 通信过程:
服务端:socket—bind—listen—while(1){—accept—recv—send—close—}---close
客户端:socket----------------------------------connect—send—recv-----------------close

服务器端创建socket,用bind绑定服务器的ip和对应进程的端口号,然后通过listen函数注册为被动socket,等待客户端进程的主动连接,通过rev接收消息,send发送消息,close关闭连接,然后继续监听等待连接

客户端创建socket,调用connect主动连接到一个被动socket,发送数据,接收回应,然后关闭连接

socket函数
  • 作用:创建一个socket,返回其文件描述符
  • 原型:int socket(int domain,int type,int protocol);
  • 参数:
    • domain:通信域, AF_INET:Ipv4 网络协议 ,AF_INET6:IPv6 网络协议,AF_UNIX:内核通信
    • typeSOCK_STREAM :TCP协议,流式socket;SOCK_DGRAM:UDP协议,数据包socket
    • protocol:指定 socket 所使用的传输协议编号。通常为 0
  • 返回值:成功则返回socket描述符,失败返回-1
bind函数
  • 作用:将一个socket与一个地址结构绑定,使其与指定的端口号和 IP 地址相关联
  • 原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
  • 参数:
    • sockfd:socket文件描述符
    • sockaddr:地址结构指针,结构体类型和地址的类型相关
    • addrlen:sizeof(struct sockaddr),地址结构体的长度
  • 返回值:成功则返回 0,失败返回-1
listen 函数
  • 作用:listen函数将一个socket注册为被动socket,用来监听等待其他socket的主动connect。一个成功connect的socket无法再注册为被动socket
  • 原型:int listen(int sockfd,int backlog);
  • 参数:sockfd为套接字描述符,backlog可以理解为该socket同时能处理的最大连接要求,通常为 5 或者 10,最大值可设至 128.。
  • 返回值:成功则返回 0,失败返回-1
accept函数
  • 作用:在监听socket上接受一个调用了connect的主动socket的连接,如果不存在接入连接则阻塞等待连接。调用accept成功后会创建一个新的socket与发起连接的socekt建立连接并进行后续通信。
  • 原型:int accept(int sfd,struct sockaddr * addr,int * addrlen);
  • 参数:
    • sfd:传入参数,socket描述符,服务端的socket描述符
    • addr:传出参数,创建的新socket的地址结构体指针,如果不需要获取该socket地址时可以传入NULL。
    • addrlen:调用时填addr指针指向的缓冲区大小,传出时变为addr指向的结构体实际占用的大小
  • 返回值:成功则返回 创建的新socket的文件描述符,失败返回-1
connect函数
  • 作用:用来请求连接一个指定ip和端口号的处于监听状态的被动socket
  • 原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
  • 参数:
    • sockfd:申请连接的主动socket的文件描述符
    • serv_addr:为结构体指针变量,存储着服务端被动socket的 IP 与端口号信息。
    • addrlen:表示serv_addr指向的结构体变量的长度
  • 返回值:成功则返回 0,失败返回-1
I/O函数

socket是文件类型,因此可以使用read和write函数进行读写。
但是,由于socket用于网络通信,因此在使用read和write函数时可能会出现一些问题。
比如write函数只负责将数据写入本地socket的缓冲区中,并不保证全部数据都能写入,可能会由于网络连接中断等原因导致数据没有完全写入,需要自行控制重新写入,
read函数虽然可以读取指定长度的字符,但是可能对方的信息还没有完全发送完毕,这时候可能只读了一部分数据。
为了解决这些问题,在socket通信中使用recv和send函数来进行读写,在这两个函数中多了控制信息参数。

recv函数
  • 作用:用新的套接字来接收远端主机传来的数据,并把数据存到由参数 buf 指向的内存空间

  • 原型:int recv(int sockfd,void *buf,int len,unsigned int flags);

  • 参数:

    • sockfd:接收方socket的文件描述符
    • buf:指向接收数据的缓冲区
    • len:表示缓冲区的长度,即指定的接收长度
    • flags0,此时和read函数没有区别;MSG_PEEK:表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容.这样下次读的时候,仍然是一样的内容.一般在有多个进程读写数据时可以使用这个标志.;MSG_WAITALL表示等到所有的信息到达时才返回.使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误.
  • 返回值:成功则返回实际接收到的字符数,可能会少于指定的接收长度(读到了文件结尾)。失败返回-1。

send函数
  • 作用:使用socket发送数据
  • 原型:int send(int sfd,const void * msg,int len,unsigned int flags);
  • 参数:前三个参数和write函数一致,sfd是发送方socket的文件描述符,重点在flags的取值:
    • flags=0:和write函数功能一致。默认为阻塞模式,即缓冲区有数据未发送时会阻塞等待
    • flags=MSG_DONTWAIT:send以非阻塞方式运行,发送缓冲区被占用时立即返回
    • flags=MSG_NOSIGNAL:send发送时对方关闭连接,如果指定该标志量则发送进程不会收到SIGPIPE信号。
  • 返回值:成功则返回实际传送出去的字符数,可能会少于指定的发送长度。失败返回-1。
  • 如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到SIGPIPE信号,进程对该信号的默认处理是进程终止
close函数

socket同样是文件,故只需调用close关闭文件的方式关闭即可。
需要注意的是,调用close关闭套接字时,双向通信的两端都会关闭,不能读也不能写。

shutdown函数
  • 作用:可以只关闭一端的连接,数据还可以在一个方向上传输。同时,关闭的时候,所有的文件描述符都会失效。
  • 原型:int shutdown(int sockfd,int how)
  • 参数:
    • socket:文件描述符
    • how
      • SHUT_RD:关闭读端 ,之后再进行读操作返回文件结尾。对等端进行写操作则会收到SIGPIPE信号,继续写会报EPIPE错误。
      • SHUT_WR:关闭写端。对等端读操作的时候会读到文件结尾,本地写操作将产生SIGPIPE信号和EPIPE错误。
      • SHUT_RDWR:关闭读写端
  • 返回值:成功返回0,失败返回-1。

UDP通信协议 编程

UDP协议协议 socket 通信过程:
服务端:socket—bind—recvfrom—sendto—close
客户端:socket----------sendto—recvfrom—close

UDP通信类似收发快递:
服务端创建数据报socket,相当于创建快递柜,调用recvfrom函数来接收数据报,没有则阻塞。sendto相当于发快递,需要指明接收方的地址才能发送出去。

recvfrom函数
  • 作用:接收一个数据报socket传来的数据报,并可以获得发送方的地址
  • 原型:
    int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
  • 参数:
    • 前四个参数和recv函数一致,
    • from参数是传出参数,传出发送方的地址结构体指针
    • fromlen参数也是传出参数,标识了from的长度
  • 返回值:成功则返回实际接收到的字符数,可能会少于指定的接收长度(读到了文件结尾)。失败返回-1。
sendto函数
  • 作用:向指定地址的socket发送数据报
  • 原型:
    int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
  • 参数:
    • 前四个参数和send函数一致,
    • to参数指定接收方的地址结构体
    • tolen参数标识了to指向的结构体的长度
  • 返回值:成功则返回实际传送出去的字符数,可能会少于指定的发送长度。失败返回-1。

linux c socket通信

阅读数 2700

Linux进程通信例子

阅读数 456

Linux i2c通信

阅读数 53

没有更多推荐了,返回首页