前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux下Socket编程入门

Linux下Socket编程入门

原创
作者头像
_咯噔_
修改2022-04-27 14:50:23
3.6K0
修改2022-04-27 14:50:23
举报
文章被收录于专栏:CS学习笔记CS学习笔记

1、网络字节序和主机字节序

网络字节序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节序采用big endian排序方式。

不同的CPU有不同的字节序类型,这些字节序是指 整数 在内存中保存的顺序,这个叫做主机字节序,有大端小端两种。

htons()--"Host to Network Short"

htonl()--"Host to Network Long"

ntohs()--"Network to Host Short"

ntohl()--"Network to Host Long"

2、几个结构体

代码语言:javascript
复制
struct sockaddr {

  unsigned short sa_family; /* 地址家族, AF_xxx */

  char sa_data[14]; /*14字节协议地址*/

};
代码语言:javascript
复制
struct sockaddr_in {

  short int sin_family; /* 通信类型 */

  unsigned short int sin_port; /* 端口 */

  struct in_addr sin_addr; /* Internet 地址 */

  unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/

};
代码语言:javascript
复制
struct in_addr {

  unsigned long s_addr;

};

3、inet_addr和inet_ntoa

函数inet_addr(),将IP地址从字符串转换成无符号长整型,注意,inet_addr()返回的地址已经是网络字节格式

代码语言:javascript
复制
ina.sin_addr.s_addr = inet_addr("132.241.5.10");

inet_ntoa()将结构体in-addr作为一个参数,不是长整形。同样需要注意的是它返回的是一个指向一个字符的指针

代码语言:javascript
复制
printf("%s",inet_ntoa(ina.sin_addr));

4、socket()函数

代码语言:javascript
复制
int socket(int domain, int type, int protocol);

domain:即协议域,又称为协议族(family)。常用的协议族有AF_INET

type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等等(socket的类型有哪些?)。

protocol:故名思意,就是指定协议。当protocol为0时,会自动选择type类型对应的默认协议。

5、bind()函数

绑定套接字到指定的IP地址和端口号

代码语言:javascript
复制
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

6、connect()

serv_addr 是保存着目的地端口和IP地址的数据结构 struct sockaddr

至于客户端,内核将自动选择一个合适的端口号

代码语言:javascript
复制
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

7、listen()

bind()之后用listen,监听绑定的端口号

代码语言:javascript
复制
int listen(int sockfd, int backlog);

使用两个队列实现,一个SYN队列(或半连接队列)和一个accept队列(或完整的连接队列)。 处于SYN RECEIVED状态的连接被添加到SYN队列,并且当它们的状态改变为ESTABLISHED时,即当接收到3次握手中的ACK分组时,将它们移动到accept队列。 显而易见,accept系统调用只是简单地从完成队列中取出连接。 在这种情况下,listen syscall的backlog参数表示完成队列的大小

8、accept()函数

accept()函数实际做的是在已完成连接队列列头返回下一个已完成连接,服务器三路握手在listen()函数之后,accept()之前, 由内核来自动完成了三路握手。

函数通过后两个参数返回客户端的sockaddr_in结构体和长度

返回值是一个新的套接字文件描述符,这样就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据

代码语言:javascript
复制
int accept(int sockfd, void *addr, int *addrlen);

9、send()和recv()函数

这两个函数用于流式套接字或者数据报套接字的通讯。flag一般为0。

代码语言:javascript
复制
int send(int sockfd, const void *msg, int len, int flags);
代码语言:javascript
复制
int recv(int sockfd, void *buf, int len, unsigned int flags);

如果你用 connect() 连接一个数据报套接字,你可以简单的调用 send() 和 recv() 来满足你的要求

10、sendto() 和 recvfrom()函数

数据报套接字使用

代码语言:javascript
复制
int sendto(int sockfd, const void *msg, int len, unsigned int flags, 

const struct sockaddr *to, int tolen);
代码语言:javascript
复制
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,  

struct sockaddr *from, int *fromlen);

11、getpeername()函数

函数 getpeername() 告诉你在连接的流式套接字上谁在另外一边

代码语言:javascript
复制
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

12、gethostname()函数

返回你程序所运行的机器的主机名字

代码语言:javascript
复制
int gethostname(char *hostname, size_t size);

13、gethostbyname()函数

多用于客户端。DNS域名服务

代码语言:javascript
复制
struct hostent *gethostbyname(const char *name);
代码语言:javascript
复制
struct hostent {

  char *h_name;

  char **h_aliases;

  int h_addrtype;

  int h_length;

  char **h_addr_list;

};  

14、TCP的例子

服务器:

代码语言:javascript
复制
#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define MYPORT 3490 /*定义用户连接端口*/

#define BACKLOG 10 /*多少等待连接控制*/

main()

{

  int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */

  struct sockaddr_in my_addr; /* my address information */

  struct sockaddr_in their_addr; /* connector's address information */

  int sin_size;

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

  perror("socket");

  exit(1);

  }

  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),; /* zero the rest of the struct */

 

  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))== -1) {

  perror("bind");

  exit(1);

  }

  if (listen(sockfd, BACKLOG) == -1) {

  perror("listen");

  exit(1);

  }

 

  while(1) { /* main accept() loop */

  sin_size = sizeof(struct sockaddr_in);

  if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {

  perror("accept");

  continue;

  }

  printf("server: got connection from %s\n", \

  inet_ntoa(their_addr.sin_addr));

  if (!fork()) { /* this is the child process */

  if (send(new_fd, "Hello, world!\n", 14, 0) == -1)

  perror("send");

  close(new_fd);

  exit(0);

  }

  close(new_fd); /* parent doesn't need this */

  while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */

}

}

客户端:

代码语言:javascript
复制
#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define PORT 3490 /* 客户机连接远程主机的端口 */

#define MAXDATASIZE 100 /* 每次可以接收的最大字节 */

int main(int argc, char *argv[])

{

int sockfd, numbytes;

char buf[MAXDATASIZE];

struct hostent *he;

struct sockaddr_in their_addr; /* connector's address information */

if (argc != 2) {

fprintf(stderr,"usage: client hostname\n");

exit(1);

}

if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */

herror("gethostbyname");

exit(1);

}

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

perror("socket");

exit(1);

}

their_addr.sin_family = AF_INET; /* host byte order */

their_addr.sin_port = htons(PORT); /* short, network byte order */

their_addr.sin_addr = *((struct in_addr *)he->h_addr);

bzero(&(their_addr.sin_zero),; /* zero the rest of the struct */

if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)) == -1) {

perror("connect");

exit(1);

}

if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {

perror("recv");

exit(1);

}

buf[numbytes] = '\0';

printf("Received: %s",buf);

close(sockfd);

return 0;

}

15、UDP的例子

服务器:

代码语言:javascript
复制
#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define MYPORT 4950 /* the port users will be sending to */

#define MAXBUFLEN 100

main()

{

int sockfd;

struct sockaddr_in my_addr; /* my address information */

struct sockaddr_in their_addr; /* connector's address information */

int addr_len, numbytes;

char buf[MAXBUFLEN];

if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {

perror("socket");

exit(1);

}

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),; /* zero the rest of the struct */

if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {

perror("bind");

exit(1);

}

addr_len = sizeof(struct sockaddr);

if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0,     \

(struct sockaddr *)&their_addr, &addr_len)) == -1) {

perror("recvfrom");

exit(1);

}

printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));

printf("packet is %d bytes long\n",numbytes);

buf[numbytes] = '\0';

printf("packet contains \"%s\"\n",buf);

close(sockfd);

}

客户端:

代码语言:javascript
复制
#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define MYPORT 4950 /* the port users will be sending to */

int main(int argc, char *argv[])

{

int sockfd;

struct sockaddr_in their_addr; /* connector's address information */

struct hostent *he;

int numbytes;

 

if (argc != 3) {

fprintf(stderr,"usage: talker hostname message\n");

exit(1);

}

 

if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */

herror("gethostbyname");

exit(1);

}

if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {

perror("socket");

exit(1);

}

their_addr.sin_family = AF_INET; /* host byte order */

their_addr.sin_port = htons(MYPORT); /* short, network byte order */

their_addr.sin_addr = *((struct in_addr *)he->h_addr);

bzero(&(their_addr.sin_zero),; /* zero the rest of the struct */

if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \

(struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {

perror("sendto");

exit(1);

}

printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr.sin_addr));

close(sockfd);

return 0;

}

16、阻塞

很多函数都利用阻塞。accept() 阻塞,所有的 recv*() 函数阻塞。它们之所以能这样做是因为它们被允许这样做。当你第一次调用 socket() 建立套接字描述符的时候,内核就将它设置为阻塞,如果你不想套接字阻塞, 你就要调用函数 fcntl():

代码语言:javascript
复制
#include <unistd.h>
#include <fontl.h>
……
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
……

让你的程序在忙等状态查询套接字的数据,这将浪费大量的 CPU 时间。更好的解决之道是用下面讲的 select() 去查询是否有数据要读进来。

17、select()--多路复用 I/O

select() 让你可以同时监视多个套接字。如果你想知道的话,那么它就会告诉你哪个套接字准备读,哪个又准备写,哪个套接字又发生了例外 (exception)。

如果你有一个正在侦听 (listen()) 的套 接字,你可以通过将该套接字的文件描述符加入到 readfds 集合中来看是否有新的连接

代码语言:javascript
复制
#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

numfds是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1

fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄。fd_set集合可以通过一些宏由人为来操作。下面有一些宏来对这个类型进行操作:

代码语言:javascript
复制
FD_ZERO(fd_set *set) – 清除一个文件描述符集合
FD_SET(int fd, fd_set *set) - 添加fd到集合
FD_CLR(int fd, fd_set *set) – 从集合中移去fd
FD_ISSET(int fd, fd_set *set) – 测试fd是否在集合中

struct timeval用来代表时间值,有两个成员,一个是秒数,另一个是微秒数。 若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

I/O multiplexing
I/O multiplexing

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、网络字节序和主机字节序
  • 2、几个结构体
  • 3、inet_addr和inet_ntoa
  • 4、socket()函数
  • 5、bind()函数
  • 6、connect()
  • 7、listen()
  • 8、accept()函数
  • 9、send()和recv()函数
  • 10、sendto() 和 recvfrom()函数
  • 11、getpeername()函数
  • 12、gethostname()函数
  • 13、gethostbyname()函数
  • 14、TCP的例子
  • 15、UDP的例子
  • 16、阻塞
  • 17、select()--多路复用 I/O
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档