Linux下Socket编程入门

2022-04-27 14:50:23 浏览数 (1)

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 %sn", 

  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 hostnamen");

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] = '';

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 %sn",inet_ntoa(their_addr.sin_addr));

printf("packet is %d bytes longn",numbytes);

buf[numbytes] = '';

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 messagen");

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 %sn",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 multiplexingI/O multiplexing

0 人点赞